@linxin666/dsh-pet 0.3.6 → 0.3.9
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 +4 -0
- package/README.zh.md +4 -0
- package/lib/client.js +187 -88
- package/lib/client.js.map +1 -1
- package/lib/index.js +89 -11
- package/lib/live2d-vendor.js +106 -95
- package/lib/live2d-vendor.js.map +1 -1
- package/lib/types/announce.d.ts +53 -0
- package/lib/types/announce.d.ts.map +1 -0
- package/lib/types/announce.js +80 -0
- package/lib/types/client/PetDockEntry.d.ts +14 -5
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +6 -5
- package/lib/types/client/PetSettingsCard.d.ts +2 -1
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSprite.d.ts +8 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +29 -4
- package/lib/types/client/index.d.ts +8 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +11 -5
- package/lib/types/client/locales.d.ts +2 -2
- package/lib/types/client/locales.js +2 -2
- package/lib/types/client/pet-store.d.ts +1 -1
- package/lib/types/client/pet-store.d.ts.map +1 -1
- package/lib/types/client/pet-store.js +1 -1
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +60 -53
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +10 -9
- package/lib/types/service.d.ts +18 -0
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +22 -0
- package/package.json +17 -13
- package/src/announce.ts +103 -0
- package/src/client/PetDockEntry.test.tsx +1 -1
- package/src/client/PetDockEntry.tsx +15 -4
- package/src/client/PetSettingsCard.tsx +2 -1
- package/src/client/PetSprite.test.tsx +18 -0
- package/src/client/PetSprite.tsx +66 -3
- package/src/client/gameplay-hud.test.tsx +1 -1
- package/src/client/index.test.tsx +2 -2
- package/src/client/index.ts +17 -6
- package/src/client/locales.ts +2 -2
- package/src/client/pet-store.ts +2 -2
- package/src/client/pet.module.css +104 -0
- package/src/client/settings-form.ts +78 -92
- package/src/dsh-home.ts +1 -1
- package/src/index.ts +17 -15
- package/src/service.ts +28 -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: 836e699d1b34cdd8708ccee4f97b3c07c0c4b73a
|
|
6
|
+
README.zh.md: 6d5cbc8d68853105df294255cb76cbbf5c2757c8
|
package/README.md
CHANGED
|
@@ -196,6 +196,10 @@ A status bubble can carry a small ornament ahead of its text (built-in: the spou
|
|
|
196
196
|
- Structure is fail-closed (unknown fields, out-of-range geometry, non-PNG/WebP entries reject with diagnostics); segment content is warn-and-drop. The machine-readable twin lives at contracts/status-decoration-v1.schema.json; the authoritative validator is src/decoration.ts.
|
|
197
197
|
- Sources: built-in assets/decorations/ plus the user directory $DSH_HOME/pets/decorations/<id>/ (same id overrides the built-in). Assets ride /api/pet/decoration/<id>/<file> with the same containment and allow-lists as pet assets.
|
|
198
198
|
- Switch: Settings > Pet > Status decoration (on by default). The built-in whale derives from the DeepSeek wordmark (MIT); see THIRD_PARTY_NOTICES.md.
|
|
199
|
+
## Announcement bubbles (pet.announce, dsh-usage linkage)
|
|
200
|
+
|
|
201
|
+
Host-side sibling plugins can push one structured announcement through the `pet` cordis service (`ctx.pet.announce({ source, kind, title, ... })`); the browser half renders it as a dedicated, specially styled bubble riding the top of the session bubble stack — its own glass style with a tone accent (`ok`/`warn`/`low`), an amount pill for balance or today-spend kinds, a mini meter for plan-percent windows, and the reset instant. The payload is validated into a bounded shape (src/announce.ts): unknown fields drop, text truncates, TTL clamps to 1 s - 2 h (default 10 s; a repeating announcer declares its poll cadence so an always-mode bubble stays continuous, and the ceiling still unmounts a dead source's bubble within one missed refresh), and a malformed announcement is dropped silently. Announcements are in-memory only: the freshest one wins, an expired one stops rendering, and neither pet.json nor the ledger is touched. There is deliberately no HTTP surface — the API is the in-process service, per the cross-plugin collaboration rule.
|
|
202
|
+
|
|
199
203
|
## Built-in pets
|
|
200
204
|
|
|
201
205
|
| Registry id | Selector label | Source |
|
package/README.zh.md
CHANGED
|
@@ -196,6 +196,10 @@ frames2d 宠物可声明 `gameplay` 块——从 miku 桌宠泛化而来的可
|
|
|
196
196
|
- 结构 fail-closed(未知字段、越界尺寸、非 PNG/WebP 入口直接拒载并进诊断),帧段内容 warn-and-drop。机器可读 schema 见 contracts/status-decoration-v1.schema.json,权威校验器为 src/decoration.ts。
|
|
197
197
|
- 来源:内置 assets/decorations/ + 用户目录 $DSH_HOME/pets/decorations/<id>/(同 id 覆盖内置)。资产经 /api/pet/decoration/<id>/<file> 路由,containment 与白名单与宠物资产同构。
|
|
198
198
|
- 开关:设置 → 宠物 → 状态装饰(默认开)。内置鲸鱼素材派生自 DeepSeek wordmark(MIT),完整声明见 THIRD_PARTY_NOTICES.md。
|
|
199
|
+
## 公告气泡(pet.announce,dsh-usage 联动)
|
|
200
|
+
|
|
201
|
+
宿主侧的兄弟插件可通过 `pet` cordis 服务推送一条结构化公告(`ctx.pet.announce({ source, kind, title, ... })`);客户端半区将其渲染为一只专用、特别设计的气泡,挂在会话气泡栈顶部——独立玻璃样式与色调描边(`ok`/`warn`/`low`)、余额与今日消费类金额胶囊、套餐百分比窗口带微型计量条、展示重置时间。载荷经有界校验(src/announce.ts):未知字段丢弃、超长文本截断、TTL 收敛到 1 秒 - 2 小时(默认 10 秒;周期型发布方按自身轮询节奏声明 TTL,常驻气泡因此跨轮询连续,上限仍保证失活来源的气泡至多一个刷新周期内消失),畸形公告静默丢弃。公告仅存内存:最新一条生效,过期即不再渲染,不触碰 pet.json 与台账。刻意不提供 HTTP 面——按跨插件协作规则,API 就是进程内服务。
|
|
202
|
+
|
|
199
203
|
## 内置宠物
|
|
200
204
|
|
|
201
205
|
| 注册表 id | 选择器名称 | 来源 |
|
package/lib/client.js
CHANGED
|
@@ -6,9 +6,21 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_dom_client = require("react-dom/client");
|
|
9
|
-
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
10
9
|
let react_dom = require("react-dom");
|
|
11
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
|
+
//#region \0dsh-store-engine
|
|
12
|
+
const platform = ["@deepseek-ai/dsh-client", "-store"].join("");
|
|
13
|
+
const legacy = ["@deepseek-ai/dsh-client-runtime", "/client"].join("");
|
|
14
|
+
let engine;
|
|
15
|
+
try {
|
|
16
|
+
engine = require(platform);
|
|
17
|
+
} catch {
|
|
18
|
+
engine = require(legacy);
|
|
19
|
+
}
|
|
20
|
+
const createSnapshotStore = engine.createSnapshotStore;
|
|
21
|
+
const defineStore = engine.defineStore;
|
|
22
|
+
engine.shallowEqual;
|
|
23
|
+
//#endregion
|
|
12
24
|
//#region src/client/pet-store.ts
|
|
13
25
|
/**
|
|
14
26
|
* Browser-side pet store: the pet state snapshot plus transient UI feedback
|
|
@@ -19,7 +31,7 @@ window.__ModuleLoader__.load({
|
|
|
19
31
|
*/
|
|
20
32
|
/** Create the pet store handle (apply world only; never module-level). */
|
|
21
33
|
function createPetStore() {
|
|
22
|
-
return
|
|
34
|
+
return defineStore({
|
|
23
35
|
init: () => ({
|
|
24
36
|
snapshot: null,
|
|
25
37
|
pets: [],
|
|
@@ -68,6 +80,12 @@ window.__ModuleLoader__.load({
|
|
|
68
80
|
return n;
|
|
69
81
|
}
|
|
70
82
|
//#endregion
|
|
83
|
+
//#region src/announce.ts
|
|
84
|
+
/** Whether an announcement is still fresh at `now`. */
|
|
85
|
+
function announcementFresh(announcement, now) {
|
|
86
|
+
return now - announcement.at < announcement.ttlMs;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
71
89
|
//#region src/state.ts
|
|
72
90
|
/**
|
|
73
91
|
* Map one activity phase onto the animation contract.
|
|
@@ -171,7 +189,7 @@ window.__ModuleLoader__.load({
|
|
|
171
189
|
}
|
|
172
190
|
//#endregion
|
|
173
191
|
//#region \0dsh-css:packages/dsh-pet/src/client/pet.module.css.mjs
|
|
174
|
-
const css$2 = ".kz2Bea_float{pointer-events:auto;-webkit-user-select:none;user-select:none;will-change:transform;contain:layout style;flex-direction:column;align-items:center;display:flex;position:fixed}.kz2Bea_sprite{image-rendering:auto;touch-action:none;contain:paint}.kz2Bea_spriteWrap{flex:none;position:relative}.kz2Bea_bubble{white-space:nowrap;color:#f4f7ff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards kz2Bea_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.kz2Bea_bubblePet{background:#4d6bfef2}.kz2Bea_bubbleFeed{background:#0369a1f2}.kz2Bea_bubbleStatus{text-overflow:ellipsis;backdrop-filter:blur(8px);letter-spacing:.02em;background:linear-gradient(160deg,#131c36e6,#070b1af0);border:1px solid #7e98ff73;max-width:min(280px,100vw - 24px);padding:5px 12px;animation:.24s ease-out kz2Bea_pet-bubble-in;overflow:hidden;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.kz2Bea_bubbleWhisper{letter-spacing:.03em;animation:.32s cubic-bezier(.22,1,.36,1) kz2Bea_pet-whisper-in}.kz2Bea_bubbleWhisper:before{content:\"「\";opacity:.7;margin-right:2px}.kz2Bea_bubbleWhisper:after{content:\"」\";opacity:.7;margin-left:2px}.kz2Bea_bubbleStack{pointer-events:auto;flex-direction:column-reverse;align-items:center;gap:4px;display:flex;position:absolute;bottom:100%}.kz2Bea_bubbleAnchor{display:inline-flex;position:relative}.kz2Bea_bubbleMore{z-index:1;color:#eef2ff;cursor:pointer;background:linear-gradient(160deg,#4a68f5f2,#2f44b8f2);border:1px solid #7e98ff8c;border-radius:999px;justify-content:center;align-items:center;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:600;line-height:1;transition:filter .12s;display:inline-flex;position:absolute;top:-9px;right:-10px;box-shadow:0 2px 6px #02061766}.kz2Bea_bubbleMore:hover{filter:brightness(1.15)}.kz2Bea_bubbleMore:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.kz2Bea_bubbleStack .kz2Bea_bubble{pointer-events:auto;margin-bottom:0;position:relative;bottom:auto}.kz2Bea_bubbleClickable{cursor:pointer;font:inherit;text-align:center;appearance:none;transition:filter .12s,box-shadow .12s}.kz2Bea_bubbleClickable:hover{filter:brightness(1.12)}.kz2Bea_bubbleClickable:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@keyframes kz2Bea_pet-bubble-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-whisper-in{0%{opacity:0;transform:translateY(8px)scale(.9)}60%{transform:translateY(-1px)scale(1.02)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-panel-in{0%{opacity:0;transform:translateY(4px)scale(.97)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.kz2Bea_panel{color:#e6ebf8;backdrop-filter:blur(10px);transform-origin:50% 0;background:linear-gradient(165deg,#131c36f2,#070b1af7);border:1px solid #7e98ff4d;border-radius:12px;flex-direction:column;gap:7px;min-width:148px;margin-top:8px;padding:10px 12px;font-size:12px;animation:.2s cubic-bezier(.22,1,.36,1) kz2Bea_pet-panel-in;display:flex;position:absolute;top:100%;box-shadow:0 8px 24px #02061773,0 0 0 1px #4d6bfe1a,inset 0 1px #e2e8ff14}.kz2Bea_panel:after{content:\"\";height:14px;position:absolute;bottom:100%;left:0;right:0}.kz2Bea_panelAbove{transform-origin:50% 100%;margin-top:0;margin-bottom:8px;top:auto;bottom:100%}.kz2Bea_panelAbove:after{top:100%;bottom:auto}.kz2Bea_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.kz2Bea_nameCell{letter-spacing:.02em;background:linear-gradient(90deg,#a9c1ff,#6c8bff 60%,#4d6bfe);color:#0000;-webkit-background-clip:text;background-clip:text;font-weight:600}.kz2Bea_statRank{color:#9db4ff;font-weight:600}.kz2Bea_statTreats{color:#7dd3fc;font-weight:600}.kz2Bea_statPoints{color:#fcd34d;font-weight:600}.kz2Bea_renameRow{align-items:center;gap:6px;display:flex}.kz2Bea_nameInput{color:#e6ebf8;background:#131c36e6;border:1px solid #7e98ff80;border-radius:6px;outline:none;flex:1;min-width:0;padding:3px 6px;font-size:12px}.kz2Bea_nameInput:focus{border-color:#4d6bfe;box-shadow:0 0 0 2px #4d6bfe73}.kz2Bea_actions{gap:6px;display:flex}.kz2Bea_action{cursor:pointer;color:#fff;background:linear-gradient(#4a68f5,#3a55e0);border:none;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;font-weight:600;transition:filter .12s,box-shadow .12s,transform .12s;box-shadow:0 2px 6px #4d6bfe4d}.kz2Bea_action:hover{filter:brightness(1.08);transform:translateY(-1px);box-shadow:0 4px 10px #4d6bfe66}.kz2Bea_action:active{filter:brightness(.94);transform:translateY(0);box-shadow:0 1px 4px #4d6bfe4d}.kz2Bea_action:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.kz2Bea_summon{color:#8ea6ff;cursor:pointer;background:#070b1abf;border:1px dashed #7e98ff99;border-radius:999px;padding:2px 10px;font-size:11px;transition:border-color .12s,color .12s,background .12s,box-shadow .12s}.kz2Bea_summon:hover{color:#c3d3ff;background:#070b1ae6;border-color:#7e98fff2}.kz2Bea_summon:active{color:#8ea6ff;border-color:#7e98ffcc}.kz2Bea_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@media (prefers-reduced-motion:reduce){.kz2Bea_bubble,.kz2Bea_bubbleStatus,.kz2Bea_bubbleWhisper,.kz2Bea_panel{opacity:1;animation:none}.kz2Bea_action,.kz2Bea_summon,.kz2Bea_bubbleMore{transition:none}}.kz2Bea_gameplayHud{width:0;height:0;position:absolute;bottom:0;left:0}.kz2Bea_gameplayModeChip{color:#cdd7ff;white-space:nowrap;pointer-events:none;background:#7e98ff2e;border-radius:999px;padding:1px 8px;font-size:10px;position:absolute;top:0;left:0;transform:translateY(-100%)translateY(-4px)}.kz2Bea_gameplayCard{color:#dfe6ff;background:#070b1aeb;border:1px solid #7e98ff40;border-radius:10px;min-width:180px;max-height:340px;padding:10px 12px;font-size:12px;position:absolute;bottom:0;left:0;overflow-y:auto;transform:none;box-shadow:0 8px 24px #0006}.kz2Bea_gameplayBars{flex-direction:column;gap:4px;margin-bottom:8px;display:flex}.kz2Bea_gameplayBarRow{align-items:center;gap:6px;display:flex}.kz2Bea_gameplayBarLabel{color:#9aa8d8;flex-shrink:0;width:34px}.kz2Bea_gameplayBarTrack{background:#7e98ff26;border-radius:3px;flex:1;height:6px;overflow:hidden}.kz2Bea_gameplayBarFill{background:linear-gradient(90deg,#7e98ff,#9db4ff);border-radius:3px;height:100%;transition:width .3s;display:block}.kz2Bea_gameplayActions{flex-wrap:wrap;gap:6px;display:flex}.kz2Bea_gameplayShopItems{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:8px;display:grid}.kz2Bea_gameplayShopItem{color:inherit;cursor:pointer;background:#7e98ff14;border:1px solid #7e98ff40;border-radius:8px;flex-direction:column;align-items:center;gap:4px;padding:8px 6px;display:flex}.kz2Bea_gameplayShopItem:hover{background:#7e98ff29;border-color:#7e98ffb3}.kz2Bea_gameplayShopItemImage{object-fit:contain;width:36px;height:36px;image-rendering:pixelated;pointer-events:none}.kz2Bea_gameplayShopItemLabel{text-align:center;font-size:11px}.kz2Bea_gameplayShopItemPrice{color:#ffd27e;font-size:10px}.kz2Bea_gameplayClose{color:#9aa8d8;cursor:pointer;background:0 0;border:none;padding:0 2px;font-size:14px;position:absolute;top:4px;right:8px}.kz2Bea_gameplayClose:hover{color:#fff}.kz2Bea_gameplayFloat{color:#3a2a00;white-space:nowrap;pointer-events:none;background:#ffd27eeb;border-radius:999px;padding:2px 10px;font-size:11px;animation:1.1s ease-out forwards kz2Bea_pet-gameplay-float;position:absolute;top:-8px;left:50%;transform:translate(-50%)}@keyframes kz2Bea_pet-gameplay-float{0%{opacity:0;transform:translate(-50%)translateY(6px)}20%{opacity:1}to{opacity:0;transform:translate(-50%)translateY(-18px)}}@media (prefers-reduced-motion:reduce){.kz2Bea_gameplayFloat{opacity:1;animation:none}.kz2Bea_gameplayBarFill{transition:none}}";
|
|
192
|
+
const css$2 = ".kz2Bea_float{pointer-events:auto;-webkit-user-select:none;user-select:none;will-change:transform;contain:layout style;flex-direction:column;align-items:center;display:flex;position:fixed}.kz2Bea_sprite{image-rendering:auto;touch-action:none;contain:paint}.kz2Bea_spriteWrap{flex:none;position:relative}.kz2Bea_bubble{white-space:nowrap;color:#f4f7ff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards kz2Bea_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.kz2Bea_bubblePet{background:#4d6bfef2}.kz2Bea_bubbleFeed{background:#0369a1f2}.kz2Bea_bubbleStatus{text-overflow:ellipsis;backdrop-filter:blur(8px);letter-spacing:.02em;background:linear-gradient(160deg,#131c36e6,#070b1af0);border:1px solid #7e98ff73;max-width:min(280px,100vw - 24px);padding:5px 12px;animation:.24s ease-out kz2Bea_pet-bubble-in;overflow:hidden;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.kz2Bea_bubbleWhisper{letter-spacing:.03em;animation:.32s cubic-bezier(.22,1,.36,1) kz2Bea_pet-whisper-in}.kz2Bea_bubbleWhisper:before{content:\"「\";opacity:.7;margin-right:2px}.kz2Bea_bubbleWhisper:after{content:\"」\";opacity:.7;margin-left:2px}.kz2Bea_bubbleStack{pointer-events:auto;flex-direction:column-reverse;align-items:center;gap:4px;display:flex;position:absolute;bottom:100%}.kz2Bea_bubbleAnchor{display:inline-flex;position:relative}.kz2Bea_bubbleMore{z-index:1;color:#eef2ff;cursor:pointer;background:linear-gradient(160deg,#4a68f5f2,#2f44b8f2);border:1px solid #7e98ff8c;border-radius:999px;justify-content:center;align-items:center;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:600;line-height:1;transition:filter .12s;display:inline-flex;position:absolute;top:-9px;right:-10px;box-shadow:0 2px 6px #02061766}.kz2Bea_bubbleMore:hover{filter:brightness(1.15)}.kz2Bea_bubbleMore:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.kz2Bea_bubbleStack .kz2Bea_bubble{pointer-events:auto;margin-bottom:0;position:relative;bottom:auto}.kz2Bea_bubbleClickable{cursor:pointer;font:inherit;text-align:center;appearance:none;transition:filter .12s,box-shadow .12s}.kz2Bea_bubbleClickable:hover{filter:brightness(1.12)}.kz2Bea_bubbleClickable:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@keyframes kz2Bea_pet-bubble-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-whisper-in{0%{opacity:0;transform:translateY(8px)scale(.9)}60%{transform:translateY(-1px)scale(1.02)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-panel-in{0%{opacity:0;transform:translateY(4px)scale(.97)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.kz2Bea_panel{color:#e6ebf8;backdrop-filter:blur(10px);transform-origin:50% 0;background:linear-gradient(165deg,#131c36f2,#070b1af7);border:1px solid #7e98ff4d;border-radius:12px;flex-direction:column;gap:7px;min-width:148px;margin-top:8px;padding:10px 12px;font-size:12px;animation:.2s cubic-bezier(.22,1,.36,1) kz2Bea_pet-panel-in;display:flex;position:absolute;top:100%;box-shadow:0 8px 24px #02061773,0 0 0 1px #4d6bfe1a,inset 0 1px #e2e8ff14}.kz2Bea_panel:after{content:\"\";height:14px;position:absolute;bottom:100%;left:0;right:0}.kz2Bea_panelAbove{transform-origin:50% 100%;margin-top:0;margin-bottom:8px;top:auto;bottom:100%}.kz2Bea_panelAbove:after{top:100%;bottom:auto}.kz2Bea_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.kz2Bea_nameCell{letter-spacing:.02em;background:linear-gradient(90deg,#a9c1ff,#6c8bff 60%,#4d6bfe);color:#0000;-webkit-background-clip:text;background-clip:text;font-weight:600}.kz2Bea_statRank{color:#9db4ff;font-weight:600}.kz2Bea_statTreats{color:#7dd3fc;font-weight:600}.kz2Bea_statPoints{color:#fcd34d;font-weight:600}.kz2Bea_renameRow{align-items:center;gap:6px;display:flex}.kz2Bea_nameInput{color:#e6ebf8;background:#131c36e6;border:1px solid #7e98ff80;border-radius:6px;outline:none;flex:1;min-width:0;padding:3px 6px;font-size:12px}.kz2Bea_nameInput:focus{border-color:#4d6bfe;box-shadow:0 0 0 2px #4d6bfe73}.kz2Bea_actions{gap:6px;display:flex}.kz2Bea_action{cursor:pointer;color:#fff;background:linear-gradient(#4a68f5,#3a55e0);border:none;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;font-weight:600;transition:filter .12s,box-shadow .12s,transform .12s;box-shadow:0 2px 6px #4d6bfe4d}.kz2Bea_action:hover{filter:brightness(1.08);transform:translateY(-1px);box-shadow:0 4px 10px #4d6bfe66}.kz2Bea_action:active{filter:brightness(.94);transform:translateY(0);box-shadow:0 1px 4px #4d6bfe4d}.kz2Bea_action:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.kz2Bea_summon{color:#8ea6ff;cursor:pointer;background:#070b1abf;border:1px dashed #7e98ff99;border-radius:999px;padding:2px 10px;font-size:11px;transition:border-color .12s,color .12s,background .12s,box-shadow .12s}.kz2Bea_summon:hover{color:#c3d3ff;background:#070b1ae6;border-color:#7e98fff2}.kz2Bea_summon:active{color:#8ea6ff;border-color:#7e98ffcc}.kz2Bea_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@media (prefers-reduced-motion:reduce){.kz2Bea_bubble,.kz2Bea_bubbleStatus,.kz2Bea_bubbleWhisper,.kz2Bea_panel{opacity:1;animation:none}.kz2Bea_action,.kz2Bea_summon,.kz2Bea_bubbleMore{transition:none}}.kz2Bea_gameplayHud{width:0;height:0;position:absolute;bottom:0;left:0}.kz2Bea_gameplayModeChip{color:#cdd7ff;white-space:nowrap;pointer-events:none;background:#7e98ff2e;border-radius:999px;padding:1px 8px;font-size:10px;position:absolute;top:0;left:0;transform:translateY(-100%)translateY(-4px)}.kz2Bea_gameplayCard{color:#dfe6ff;background:#070b1aeb;border:1px solid #7e98ff40;border-radius:10px;min-width:180px;max-height:340px;padding:10px 12px;font-size:12px;position:absolute;bottom:0;left:0;overflow-y:auto;transform:none;box-shadow:0 8px 24px #0006}.kz2Bea_gameplayBars{flex-direction:column;gap:4px;margin-bottom:8px;display:flex}.kz2Bea_gameplayBarRow{align-items:center;gap:6px;display:flex}.kz2Bea_gameplayBarLabel{color:#9aa8d8;flex-shrink:0;width:34px}.kz2Bea_gameplayBarTrack{background:#7e98ff26;border-radius:3px;flex:1;height:6px;overflow:hidden}.kz2Bea_gameplayBarFill{background:linear-gradient(90deg,#7e98ff,#9db4ff);border-radius:3px;height:100%;transition:width .3s;display:block}.kz2Bea_gameplayActions{flex-wrap:wrap;gap:6px;display:flex}.kz2Bea_gameplayShopItems{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:8px;display:grid}.kz2Bea_gameplayShopItem{color:inherit;cursor:pointer;background:#7e98ff14;border:1px solid #7e98ff40;border-radius:8px;flex-direction:column;align-items:center;gap:4px;padding:8px 6px;display:flex}.kz2Bea_gameplayShopItem:hover{background:#7e98ff29;border-color:#7e98ffb3}.kz2Bea_gameplayShopItemImage{object-fit:contain;width:36px;height:36px;image-rendering:pixelated;pointer-events:none}.kz2Bea_gameplayShopItemLabel{text-align:center;font-size:11px}.kz2Bea_gameplayShopItemPrice{color:#ffd27e;font-size:10px}.kz2Bea_gameplayClose{color:#9aa8d8;cursor:pointer;background:0 0;border:none;padding:0 2px;font-size:14px;position:absolute;top:4px;right:8px}.kz2Bea_gameplayClose:hover{color:#fff}.kz2Bea_gameplayFloat{color:#3a2a00;white-space:nowrap;pointer-events:none;background:#ffd27eeb;border-radius:999px;padding:2px 10px;font-size:11px;animation:1.1s ease-out forwards kz2Bea_pet-gameplay-float;position:absolute;top:-8px;left:50%;transform:translate(-50%)}@keyframes kz2Bea_pet-gameplay-float{0%{opacity:0;transform:translate(-50%)translateY(6px)}20%{opacity:1}to{opacity:0;transform:translate(-50%)translateY(-18px)}}@media (prefers-reduced-motion:reduce){.kz2Bea_gameplayFloat{opacity:1;animation:none}.kz2Bea_gameplayBarFill{transition:none}}.kz2Bea_bubbleUsage{white-space:nowrap;backdrop-filter:blur(8px);letter-spacing:.02em;background:linear-gradient(160deg,#131c36eb,#070b1af2);border:1px solid #7e98ff73;flex-direction:column;align-items:stretch;gap:3px;min-width:128px;max-width:min(280px,100vw - 24px);padding:6px 12px;animation:.28s cubic-bezier(.22,1,.36,1) kz2Bea_pet-usage-in;display:flex;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.kz2Bea_bubbleUsageHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.kz2Bea_bubbleUsageTitle{text-overflow:ellipsis;opacity:.85;overflow:hidden}.kz2Bea_bubbleUsageValue{font-variant-numeric:tabular-nums;flex-shrink:0;font-weight:600}.kz2Bea_bubbleUsageNote{text-overflow:ellipsis;opacity:.55;font-size:10px;overflow:hidden}.kz2Bea_bubbleUsageMeter{background:#e2e8ff24;border-radius:999px;height:4px;display:block;overflow:hidden}.kz2Bea_bubbleUsageMeterFill{background:#6ee7b7;border-radius:999px;height:100%;transition:width .3s;display:block}.kz2Bea_bubbleUsageOk{border-color:#7e98ff73}.kz2Bea_bubbleUsageWarn{border-color:#fbbf248c}.kz2Bea_bubbleUsageWarn .kz2Bea_bubbleUsageMeterFill{background:#fbbf24}.kz2Bea_bubbleUsageLow{border-color:#f8717199}.kz2Bea_bubbleUsageLow .kz2Bea_bubbleUsageMeterFill{background:#f87171}@keyframes kz2Bea_pet-usage-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@media (prefers-reduced-motion:reduce){.kz2Bea_bubbleUsage{animation:none}}";
|
|
175
193
|
const tagId$2 = "@linxin666/dsh-pet/pet.module.css";
|
|
176
194
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
177
195
|
const tag = document.createElement("style");
|
|
@@ -191,6 +209,16 @@ window.__ModuleLoader__.load({
|
|
|
191
209
|
"bubblePet": "kz2Bea_bubblePet",
|
|
192
210
|
"bubbleStack": "kz2Bea_bubbleStack",
|
|
193
211
|
"bubbleStatus": "kz2Bea_bubbleStatus",
|
|
212
|
+
"bubbleUsage": "kz2Bea_bubbleUsage",
|
|
213
|
+
"bubbleUsageHead": "kz2Bea_bubbleUsageHead",
|
|
214
|
+
"bubbleUsageLow": "kz2Bea_bubbleUsageLow",
|
|
215
|
+
"bubbleUsageMeter": "kz2Bea_bubbleUsageMeter",
|
|
216
|
+
"bubbleUsageMeterFill": "kz2Bea_bubbleUsageMeterFill",
|
|
217
|
+
"bubbleUsageNote": "kz2Bea_bubbleUsageNote",
|
|
218
|
+
"bubbleUsageOk": "kz2Bea_bubbleUsageOk",
|
|
219
|
+
"bubbleUsageTitle": "kz2Bea_bubbleUsageTitle",
|
|
220
|
+
"bubbleUsageValue": "kz2Bea_bubbleUsageValue",
|
|
221
|
+
"bubbleUsageWarn": "kz2Bea_bubbleUsageWarn",
|
|
194
222
|
"bubbleWhisper": "kz2Bea_bubbleWhisper",
|
|
195
223
|
"float": "kz2Bea_float",
|
|
196
224
|
"gameplayActions": "kz2Bea_gameplayActions",
|
|
@@ -217,6 +245,7 @@ window.__ModuleLoader__.load({
|
|
|
217
245
|
"pet-bubble-pop": "kz2Bea_pet-bubble-pop",
|
|
218
246
|
"pet-gameplay-float": "kz2Bea_pet-gameplay-float",
|
|
219
247
|
"pet-panel-in": "kz2Bea_pet-panel-in",
|
|
248
|
+
"pet-usage-in": "kz2Bea_pet-usage-in",
|
|
220
249
|
"pet-whisper-in": "kz2Bea_pet-whisper-in",
|
|
221
250
|
"rankRow": "kz2Bea_rankRow",
|
|
222
251
|
"renameRow": "kz2Bea_renameRow",
|
|
@@ -319,6 +348,54 @@ window.__ModuleLoader__.load({
|
|
|
319
348
|
});
|
|
320
349
|
}
|
|
321
350
|
/**
|
|
351
|
+
* The announcement bubble (dsh-usage linkage): a dedicated, specially
|
|
352
|
+
* designed surface for sibling-plugin facts — a balance or today-spend pill,
|
|
353
|
+
* or a plan-quota card with a tone-tinted accent, a mini meter for percent
|
|
354
|
+
* windows, and the reset instant. It rides the top of the session bubble
|
|
355
|
+
* stack (column-reverse puts the DOM-last child farthest from the sprite)
|
|
356
|
+
* and persists for its TTL instead of the short feedback pop.
|
|
357
|
+
*/
|
|
358
|
+
function UsageAnnouncementBubble(props) {
|
|
359
|
+
const { announcement } = props;
|
|
360
|
+
const tone = announcement.tone === "low" ? pet_module_css_default.bubbleUsageLow : announcement.tone === "warn" ? pet_module_css_default.bubbleUsageWarn : pet_module_css_default.bubbleUsageOk;
|
|
361
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
362
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleUsage, tone),
|
|
363
|
+
role: "status",
|
|
364
|
+
"aria-live": "polite",
|
|
365
|
+
"data-dsh-pet-announcement": announcement.source,
|
|
366
|
+
children: [
|
|
367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
368
|
+
className: pet_module_css_default.bubbleUsageHead,
|
|
369
|
+
children: [
|
|
370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
371
|
+
className: pet_module_css_default.bubbleUsageTitle,
|
|
372
|
+
children: announcement.title
|
|
373
|
+
}),
|
|
374
|
+
(announcement.kind === "balance" || announcement.kind === "cost") && announcement.amount !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
375
|
+
className: pet_module_css_default.bubbleUsageValue,
|
|
376
|
+
children: announcement.amount
|
|
377
|
+
}),
|
|
378
|
+
announcement.kind === "plan" && announcement.percent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
379
|
+
className: pet_module_css_default.bubbleUsageValue,
|
|
380
|
+
children: Math.round(announcement.percent) + "%"
|
|
381
|
+
})
|
|
382
|
+
]
|
|
383
|
+
}),
|
|
384
|
+
announcement.kind === "plan" && announcement.percent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
385
|
+
className: pet_module_css_default.bubbleUsageMeter,
|
|
386
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
387
|
+
className: pet_module_css_default.bubbleUsageMeterFill,
|
|
388
|
+
style: { width: Math.min(100, Math.max(0, announcement.percent)) + "%" }
|
|
389
|
+
})
|
|
390
|
+
}),
|
|
391
|
+
announcement.note !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
392
|
+
className: pet_module_css_default.bubbleUsageNote,
|
|
393
|
+
children: announcement.note
|
|
394
|
+
})
|
|
395
|
+
]
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
322
399
|
* The floating pet. The spritesheet frame advances on requestAnimationFrame
|
|
323
400
|
* with per-frame durations from the definition's tracks; the atlas image is
|
|
324
401
|
* loaded once and the background position is written straight to the sprite
|
|
@@ -517,7 +594,9 @@ window.__ModuleLoader__.load({
|
|
|
517
594
|
const stackOpen = stackPeek || stackPinned;
|
|
518
595
|
const visibleSessions = !stackOpen && sessionBubbles.length > 1 ? sessionBubbles.slice(0, 1) : sessionBubbles;
|
|
519
596
|
const statusBubble = feedback === null && sessionBubbles.length === 0 ? snapshot?.bubble : void 0;
|
|
520
|
-
const
|
|
597
|
+
const announcement = snapshot?.announcement;
|
|
598
|
+
const usageAnnouncement = feedback === null && announcement !== void 0 && announcementFresh(announcement, Date.now()) ? announcement : void 0;
|
|
599
|
+
const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== void 0 || usageAnnouncement !== void 0;
|
|
521
600
|
const displayName = snapshot?.name ?? definition.displayName;
|
|
522
601
|
const decoration = snapshot?.decoration;
|
|
523
602
|
(0, react.useEffect)(() => {
|
|
@@ -614,50 +693,54 @@ window.__ModuleLoader__.load({
|
|
|
614
693
|
className: clsx(pet_module_css_default.bubble, feedback.kind === "feed" ? pet_module_css_default.bubbleFeed : pet_module_css_default.bubblePet),
|
|
615
694
|
children: feedback.text
|
|
616
695
|
}, feedback.at),
|
|
617
|
-
feedback === null && (sessionBubbles.length > 0 || statusBubble !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
696
|
+
feedback === null && (sessionBubbles.length > 0 || statusBubble !== void 0 || usageAnnouncement !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
618
697
|
ref: bubbleRef,
|
|
619
698
|
className: pet_module_css_default.bubbleStack,
|
|
620
699
|
onPointerEnter: () => setStackPeek(true),
|
|
621
700
|
onPointerLeave: () => setStackPeek(false),
|
|
622
|
-
children: [
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, pet_module_css_default.bubbleClickable, speaksWhisper && pet_module_css_default.bubbleWhisper),
|
|
627
|
-
title: props.t("pet.openSessionHint"),
|
|
628
|
-
onClick: () => {
|
|
629
|
-
props.onOpenSession(session.sessionId);
|
|
630
|
-
},
|
|
631
|
-
children: [index === 0 && !speaksWhisper && decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
632
|
-
decoration,
|
|
633
|
-
phase
|
|
634
|
-
}), session.whisper ?? session.bubble]
|
|
635
|
-
}, speaksWhisper ? "whisper:" + session.whisper : session.sessionId);
|
|
636
|
-
if (index !== 0 || sessionBubbles.length <= 1) return bubble;
|
|
637
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
638
|
-
className: pet_module_css_default.bubbleAnchor,
|
|
639
|
-
children: [bubble, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
701
|
+
children: [
|
|
702
|
+
visibleSessions.map((session, index) => {
|
|
703
|
+
const speaksWhisper = session.whisper !== void 0;
|
|
704
|
+
const bubble = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
640
705
|
type: "button",
|
|
641
|
-
className: pet_module_css_default.
|
|
642
|
-
title:
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
onClick: (e) => {
|
|
646
|
-
e.stopPropagation();
|
|
647
|
-
setStackPinned((open) => !open);
|
|
706
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, pet_module_css_default.bubbleClickable, speaksWhisper && pet_module_css_default.bubbleWhisper),
|
|
707
|
+
title: props.t("pet.openSessionHint"),
|
|
708
|
+
onClick: () => {
|
|
709
|
+
props.onOpenSession(session.sessionId);
|
|
648
710
|
},
|
|
649
|
-
children:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
711
|
+
children: [index === 0 && !speaksWhisper && decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
712
|
+
decoration,
|
|
713
|
+
phase
|
|
714
|
+
}), session.whisper ?? session.bubble]
|
|
715
|
+
}, speaksWhisper ? "whisper:" + session.whisper : session.sessionId);
|
|
716
|
+
if (index !== 0 || sessionBubbles.length <= 1) return bubble;
|
|
717
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
718
|
+
className: pet_module_css_default.bubbleAnchor,
|
|
719
|
+
children: [bubble, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
720
|
+
type: "button",
|
|
721
|
+
className: pet_module_css_default.bubbleMore,
|
|
722
|
+
title: stackOpen ? props.t("pet.collapseSessions") : props.t("pet.moreSessions", { n: sessionBubbles.length - 1 }),
|
|
723
|
+
"aria-label": stackOpen ? props.t("pet.collapseSessions") : props.t("pet.moreSessions", { n: sessionBubbles.length - 1 }),
|
|
724
|
+
"aria-expanded": stackOpen,
|
|
725
|
+
onClick: (e) => {
|
|
726
|
+
e.stopPropagation();
|
|
727
|
+
setStackPinned((open) => !open);
|
|
728
|
+
},
|
|
729
|
+
children: stackOpen ? "×" : "+" + String(sessionBubbles.length - 1)
|
|
730
|
+
})]
|
|
731
|
+
}, "primary");
|
|
732
|
+
}),
|
|
733
|
+
sessionBubbles.length === 0 && statusBubble !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
734
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus),
|
|
735
|
+
role: "status",
|
|
736
|
+
"aria-live": "polite",
|
|
737
|
+
children: [decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
738
|
+
decoration,
|
|
739
|
+
phase
|
|
740
|
+
}), statusBubble]
|
|
741
|
+
}, "status"),
|
|
742
|
+
usageAnnouncement !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageAnnouncementBubble, { announcement: usageAnnouncement })
|
|
743
|
+
]
|
|
661
744
|
}),
|
|
662
745
|
hovered && dragRef.current === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
663
746
|
ref: panelRef,
|
|
@@ -766,7 +849,7 @@ window.__ModuleLoader__.load({
|
|
|
766
849
|
] })
|
|
767
850
|
})
|
|
768
851
|
]
|
|
769
|
-
}), document.body);
|
|
852
|
+
}), props.portalTarget ?? document.body);
|
|
770
853
|
}
|
|
771
854
|
//#endregion
|
|
772
855
|
//#region src/client/drag-stream.ts
|
|
@@ -1436,10 +1519,11 @@ window.__ModuleLoader__.load({
|
|
|
1436
1519
|
* it must not ride a session-scoped slot — on the new-conversation screen no
|
|
1437
1520
|
* session exists to scope a slot by, and the pet would vanish (issue #48).
|
|
1438
1521
|
* The client half therefore mounts this entry straight onto 'document.body'
|
|
1439
|
-
* (see index.ts): while visible it renders the floating PetSprite (a
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
1442
|
-
* the registry list — no per-pet
|
|
1522
|
+
* (see index.ts): while visible it renders the floating PetSprite (a portal
|
|
1523
|
+
* into the plugin root, so the root owns the whole surface), while hidden it
|
|
1524
|
+
* renders a fixed-position summon button. Which sprite renders is decided by
|
|
1525
|
+
* the host snapshot's pet id resolved against the registry list — no per-pet
|
|
1526
|
+
* component exists.
|
|
1443
1527
|
* @module @linxin666/dsh-pet/client/PetDockEntry
|
|
1444
1528
|
*/
|
|
1445
1529
|
const DEFAULT_DISPLAY = {
|
|
@@ -1497,6 +1581,7 @@ window.__ModuleLoader__.load({
|
|
|
1497
1581
|
onRename: props.rename,
|
|
1498
1582
|
onOpenSession: props.openSession,
|
|
1499
1583
|
onFeedbackDone: props.feedbackDone,
|
|
1584
|
+
portalTarget: props.portalTarget,
|
|
1500
1585
|
dragDisabled: snapshot.gameplay?.mode === "work",
|
|
1501
1586
|
...gameplay === void 0 || aux === null ? {} : {
|
|
1502
1587
|
onGameplayTap: (fx, fy) => aux.bus.tap?.(fx, fy),
|
|
@@ -2694,7 +2779,7 @@ window.__ModuleLoader__.load({
|
|
|
2694
2779
|
}
|
|
2695
2780
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
2696
2781
|
bind(project) {
|
|
2697
|
-
const store =
|
|
2782
|
+
const store = createSnapshotStore(project());
|
|
2698
2783
|
this.listeners.add(() => {
|
|
2699
2784
|
store.set(project());
|
|
2700
2785
|
});
|
|
@@ -2709,7 +2794,7 @@ window.__ModuleLoader__.load({
|
|
|
2709
2794
|
exposed: snapshot.status === "ready",
|
|
2710
2795
|
writable: snapshot.writable,
|
|
2711
2796
|
dirty: plan.length > 0,
|
|
2712
|
-
invalid: plan.some((item) => item.
|
|
2797
|
+
invalid: plan.some((item) => item.judge === void 0),
|
|
2713
2798
|
saving: this.saving,
|
|
2714
2799
|
failed: this.failed,
|
|
2715
2800
|
...this.failedReason === void 0 ? {} : { failedReason: this.failedReason }
|
|
@@ -2759,45 +2844,52 @@ window.__ModuleLoader__.load({
|
|
|
2759
2844
|
};
|
|
2760
2845
|
}
|
|
2761
2846
|
/**
|
|
2762
|
-
* Write every staged edit, then re-seed from
|
|
2847
|
+
* Write every staged edit in one atomic scope mutation, then re-seed from
|
|
2848
|
+
* what the Host accepted.
|
|
2763
2849
|
*
|
|
2764
|
-
*
|
|
2765
|
-
*
|
|
2766
|
-
*
|
|
2767
|
-
*
|
|
2768
|
-
*
|
|
2769
|
-
*
|
|
2770
|
-
*
|
|
2850
|
+
* The whole batch rides one mutate, so cross-field validate hooks
|
|
2851
|
+
* (baseURL+model) judge it as a unit: the Host either applies every write
|
|
2852
|
+
* or refuses the batch. The 0.1.2 scope contract never rejects a refused
|
|
2853
|
+
* mutation — the scope recovers with a fresh Host view and resolves — so
|
|
2854
|
+
* resolution alone proves nothing: the outcome is judged by reading the
|
|
2855
|
+
* settled snapshot back, one planned write at a time, and one missed write
|
|
2856
|
+
* fails the whole save. A scope that still rejects on refusal (the dsh-web
|
|
2857
|
+
* bridge scope) reports through the same failure path with its rejection
|
|
2858
|
+
* message. A save that did not land keeps its drafts, so the user can
|
|
2859
|
+
* correct them instead of retyping.
|
|
2860
|
+
* @returns settlement after the mutation and the read-back.
|
|
2771
2861
|
*/
|
|
2772
2862
|
async save() {
|
|
2773
2863
|
const plan = this.plan();
|
|
2774
|
-
const valid = plan.filter((item) => item.
|
|
2864
|
+
const valid = plan.filter((item) => item.judge !== void 0);
|
|
2775
2865
|
if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
|
|
2776
|
-
const plannedWrites = valid.map((item) => item.op);
|
|
2777
2866
|
const pending = /* @__PURE__ */ new Map();
|
|
2778
2867
|
for (const item of plan) pending.set(item.field, this.staged.get(item.field));
|
|
2779
2868
|
this.saving = true;
|
|
2780
2869
|
this.failed = false;
|
|
2781
2870
|
this.failedReason = void 0;
|
|
2782
2871
|
this.publish();
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
}
|
|
2791
|
-
|
|
2872
|
+
const ops = valid.map((item) => item.op.op === "set" ? {
|
|
2873
|
+
op: "set",
|
|
2874
|
+
path: [item.field],
|
|
2875
|
+
value: item.op.value
|
|
2876
|
+
} : {
|
|
2877
|
+
op: "unset",
|
|
2878
|
+
path: [item.field]
|
|
2879
|
+
});
|
|
2880
|
+
let failedReason;
|
|
2881
|
+
try {
|
|
2882
|
+
await this.scope.mutate(ops);
|
|
2883
|
+
} catch (error) {
|
|
2884
|
+
failedReason = error instanceof Error ? error.message : String(error);
|
|
2885
|
+
}
|
|
2886
|
+
const landed = failedReason === void 0 && valid.every((item) => item.judge());
|
|
2887
|
+
for (const [field, before] of pending) if (landed && this.staged.get(field) === before) this.staged.delete(field);
|
|
2792
2888
|
this.saving = false;
|
|
2793
|
-
this.failed = landed
|
|
2889
|
+
this.failed = !landed;
|
|
2890
|
+
this.failedReason = failedReason;
|
|
2794
2891
|
this.publish();
|
|
2795
2892
|
}
|
|
2796
|
-
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
2797
|
-
batchedScope() {
|
|
2798
|
-
const candidate = this.scope;
|
|
2799
|
-
return typeof candidate?.mutate === "function" ? candidate : void 0;
|
|
2800
|
-
}
|
|
2801
2893
|
/**
|
|
2802
2894
|
* Every staged edit a save would write. An entry whose draft is not a value
|
|
2803
2895
|
* its field accepts carries no write: the form is still dirty, and the save
|
|
@@ -2816,7 +2908,7 @@ window.__ModuleLoader__.load({
|
|
|
2816
2908
|
field,
|
|
2817
2909
|
op: "unset"
|
|
2818
2910
|
},
|
|
2819
|
-
|
|
2911
|
+
judge: () => this.landedUnset(field)
|
|
2820
2912
|
});
|
|
2821
2913
|
continue;
|
|
2822
2914
|
}
|
|
@@ -2828,7 +2920,7 @@ window.__ModuleLoader__.load({
|
|
|
2828
2920
|
field,
|
|
2829
2921
|
op: "unset"
|
|
2830
2922
|
},
|
|
2831
|
-
|
|
2923
|
+
judge: void 0
|
|
2832
2924
|
});
|
|
2833
2925
|
else if (write.kind === "clear") plan.push({
|
|
2834
2926
|
field,
|
|
@@ -2836,7 +2928,7 @@ window.__ModuleLoader__.load({
|
|
|
2836
2928
|
field,
|
|
2837
2929
|
op: "unset"
|
|
2838
2930
|
},
|
|
2839
|
-
|
|
2931
|
+
judge: () => this.landedUnset(field)
|
|
2840
2932
|
});
|
|
2841
2933
|
else plan.push({
|
|
2842
2934
|
field,
|
|
@@ -2845,20 +2937,26 @@ window.__ModuleLoader__.load({
|
|
|
2845
2937
|
op: "set",
|
|
2846
2938
|
value: write.value
|
|
2847
2939
|
},
|
|
2848
|
-
|
|
2940
|
+
judge: () => this.landedSet(field, write.value)
|
|
2849
2941
|
});
|
|
2850
2942
|
}
|
|
2851
2943
|
return plan;
|
|
2852
2944
|
}
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
await this.scope.set(field, value);
|
|
2945
|
+
/**
|
|
2946
|
+
* Read-back judgment for a planned set: the user layer must hold the
|
|
2947
|
+
* intended value once the mutation has settled.
|
|
2948
|
+
*/
|
|
2949
|
+
landedSet(field, value) {
|
|
2859
2950
|
if (this.specOf(field).secret) return true;
|
|
2860
2951
|
return this.userLayer()?.[field] === value;
|
|
2861
2952
|
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Read-back judgment for a planned unset: the field must be gone from the
|
|
2955
|
+
* user layer once the mutation has settled.
|
|
2956
|
+
*/
|
|
2957
|
+
landedUnset(field) {
|
|
2958
|
+
return !this.stored(field);
|
|
2959
|
+
}
|
|
2862
2960
|
stage(field, edit) {
|
|
2863
2961
|
this.staged.set(field, edit);
|
|
2864
2962
|
this.failed = false;
|
|
@@ -3250,7 +3348,7 @@ window.__ModuleLoader__.load({
|
|
|
3250
3348
|
"settings.off": "关",
|
|
3251
3349
|
"settings.overridden": "已覆盖",
|
|
3252
3350
|
"settings.reset": "恢复默认",
|
|
3253
|
-
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑
|
|
3351
|
+
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 $DSH_HOME/settings.yaml 直接配置,或确认提供该命名空间的插件已挂载其设置域并重启。",
|
|
3254
3352
|
"settings.readOnly": "当前部署的设置只读。",
|
|
3255
3353
|
"settings.expand": "展开设置",
|
|
3256
3354
|
"settings.collapse": "收起设置",
|
|
@@ -3329,7 +3427,7 @@ window.__ModuleLoader__.load({
|
|
|
3329
3427
|
"settings.off": "Off",
|
|
3330
3428
|
"settings.overridden": "Overridden",
|
|
3331
3429
|
"settings.reset": "Reset to default",
|
|
3332
|
-
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit
|
|
3430
|
+
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit $DSH_HOME/settings.yaml directly, or confirm that the plugin owning the namespace is mounted with its settings domain and restart.",
|
|
3333
3431
|
"settings.readOnly": "This deployment stores settings read-only.",
|
|
3334
3432
|
"settings.expand": "Show settings",
|
|
3335
3433
|
"settings.collapse": "Hide settings",
|
|
@@ -3370,7 +3468,7 @@ window.__ModuleLoader__.load({
|
|
|
3370
3468
|
/** The building package's version, when the bundle carries it. */
|
|
3371
3469
|
function bakedVersion() {
|
|
3372
3470
|
try {
|
|
3373
|
-
return "0.3.
|
|
3471
|
+
return "0.3.9";
|
|
3374
3472
|
} catch {
|
|
3375
3473
|
return;
|
|
3376
3474
|
}
|
|
@@ -3652,7 +3750,8 @@ window.__ModuleLoader__.load({
|
|
|
3652
3750
|
const petRoot = (0, react_dom_client.createRoot)(container);
|
|
3653
3751
|
petRoot.render((0, react.createElement)(PetDockEntry, {
|
|
3654
3752
|
...injected(),
|
|
3655
|
-
t
|
|
3753
|
+
t,
|
|
3754
|
+
portalTarget: container
|
|
3656
3755
|
}));
|
|
3657
3756
|
let uiGone = false;
|
|
3658
3757
|
disposeUi = () => {
|