@linxin666/dsh-pet 0.1.14 → 0.1.15

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.
Files changed (44) hide show
  1. package/lib/client.js +29 -16
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +355 -251
  4. package/lib/invariant.js +1 -1
  5. package/lib/{state-P-wsVJ6O.js → state-CFyJv0sQ.js} +15 -1
  6. package/lib/types/affinity.d.ts +15 -0
  7. package/lib/types/affinity.d.ts.map +1 -1
  8. package/lib/types/affinity.js +14 -0
  9. package/lib/types/client/PluginSettingsCard.d.ts +26 -11
  10. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  11. package/lib/types/client/PluginSettingsCard.js +27 -7
  12. package/lib/types/client/index.d.ts +1 -1
  13. package/lib/types/client/index.js +3 -3
  14. package/lib/types/client/settings-form.d.ts +14 -5
  15. package/lib/types/client/settings-form.d.ts.map +1 -1
  16. package/lib/types/client/settings-form.js +38 -10
  17. package/lib/types/dsh-home.d.ts +17 -0
  18. package/lib/types/dsh-home.d.ts.map +1 -0
  19. package/lib/types/dsh-home.js +34 -0
  20. package/lib/types/event-projection.d.ts +36 -0
  21. package/lib/types/event-projection.d.ts.map +1 -0
  22. package/lib/types/event-projection.js +98 -0
  23. package/lib/types/ledger.d.ts +77 -0
  24. package/lib/types/ledger.d.ts.map +1 -0
  25. package/lib/types/ledger.js +139 -0
  26. package/lib/types/persist.d.ts +5 -1
  27. package/lib/types/persist.d.ts.map +1 -1
  28. package/lib/types/persist.js +7 -3
  29. package/lib/types/service.d.ts +15 -46
  30. package/lib/types/service.d.ts.map +1 -1
  31. package/lib/types/service.js +47 -204
  32. package/package.json +1 -1
  33. package/src/affinity.ts +33 -0
  34. package/src/client/PluginSettingsCard.tsx +83 -17
  35. package/src/client/index.ts +3 -3
  36. package/src/client/settings-card.module.css +6 -0
  37. package/src/client/settings-form.ts +41 -9
  38. package/src/dsh-home.test.ts +35 -0
  39. package/src/dsh-home.ts +36 -0
  40. package/src/event-projection.ts +128 -0
  41. package/src/ledger.test.ts +67 -0
  42. package/src/ledger.ts +183 -0
  43. package/src/persist.ts +7 -3
  44. package/src/service.ts +60 -274
package/lib/client.js CHANGED
@@ -686,7 +686,7 @@ window.__ModuleLoader__.load({
686
686
  }
687
687
  //#endregion
688
688
  //#region \0dsh-css:packages/dsh-pet/src/client/settings-card.module.css.mjs
689
- const css = ".kKk9aW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.kKk9aW_card:hover{border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.kKk9aW_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.kKk9aW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.kKk9aW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.kKk9aW_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.kKk9aW_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kKk9aW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.kKk9aW_chevronOpen{transform:rotate(180deg)}.kKk9aW_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.kKk9aW_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.kKk9aW_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.kKk9aW_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.kKk9aW_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.kKk9aW_discard,.kKk9aW_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.kKk9aW_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.kKk9aW_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.kKk9aW_discard:disabled,.kKk9aW_save:disabled{opacity:.4;cursor:default}.kKk9aW_discard:focus-visible,.kKk9aW_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.kKk9aW_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.kKk9aW_field+.kKk9aW_field{border-top:1px solid var(--dsw-alias-border-l2)}.kKk9aW_head{align-items:center;gap:8px;display:flex}.kKk9aW_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.kKk9aW_badges{align-items:center;gap:8px;display:inline-flex}.kKk9aW_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kKk9aW_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.kKk9aW_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.kKk9aW_reset:disabled{cursor:default}.kKk9aW_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.kKk9aW_input,.kKk9aW_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.kKk9aW_input:focus-visible,.kKk9aW_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.kKk9aW_input:disabled,.kKk9aW_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.kKk9aW_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.kKk9aW_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.kKk9aW_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.kKk9aW_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.kKk9aW_card,.kKk9aW_header,.kKk9aW_chevron,.kKk9aW_chevronOpen,.kKk9aW_discard,.kKk9aW_save{transition:none}}";
689
+ const css = ".kKk9aW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.kKk9aW_card:hover{border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.kKk9aW_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.kKk9aW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.kKk9aW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.kKk9aW_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.kKk9aW_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kKk9aW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.kKk9aW_chevronOpen{transform:rotate(180deg)}.kKk9aW_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.kKk9aW_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.kKk9aW_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.kKk9aW_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.kKk9aW_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.kKk9aW_discard,.kKk9aW_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.kKk9aW_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.kKk9aW_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.kKk9aW_discard:disabled,.kKk9aW_save:disabled{opacity:.4;cursor:default}.kKk9aW_discard:focus-visible,.kKk9aW_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.kKk9aW_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.kKk9aW_field+.kKk9aW_field{border-top:1px solid var(--dsw-alias-border-l2)}.kKk9aW_head{align-items:center;gap:8px;display:flex}.kKk9aW_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.kKk9aW_badges{align-items:center;gap:8px;display:inline-flex}.kKk9aW_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kKk9aW_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.kKk9aW_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.kKk9aW_reset:disabled{cursor:default}.kKk9aW_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.kKk9aW_input,.kKk9aW_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.kKk9aW_input:focus-visible,.kKk9aW_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.kKk9aW_input:disabled,.kKk9aW_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.kKk9aW_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.kKk9aW_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.kKk9aW_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.kKk9aW_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.kKk9aW_card,.kKk9aW_header,.kKk9aW_chevron,.kKk9aW_chevronOpen,.kKk9aW_discard,.kKk9aW_save{transition:none}}";
690
690
  const tagId = "@linxin666/dsh-pet/settings-card.module.css";
691
691
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
692
692
  const tag = document.createElement("style");
@@ -727,12 +727,12 @@ window.__ModuleLoader__.load({
727
727
  //#endregion
728
728
  //#region src/client/PluginSettingsCard.tsx
729
729
  /**
730
- * Shared chrome for the plugin settings card: a disclosure header naming the
731
- * plugin and what its settings govern, the controls inside, and the save that
732
- * writes them. Renders nothing while the namespace is unavailable — a
730
+ * Family-shared chrome for plugin settings cards: a disclosure header naming
731
+ * the plugin and what its settings govern, the controls inside, and the save
732
+ * that writes them. Renders nothing while the namespace is unavailable — a
733
733
  * deployment that does not compose the owning plugin should show no trace of
734
- * it. Mirrors the official ui-plugin-config PluginCard in a self-contained
735
- * slice (this package must not depend on a sibling UI package).
734
+ * it. Inlined into each consumer's client bundle; mirrors the official
735
+ * ui-plugin-config PluginCard in a self-contained slice.
736
736
  */
737
737
  /**
738
738
  * Render one plugin settings card.
@@ -744,6 +744,7 @@ window.__ModuleLoader__.load({
744
744
  const { state } = props;
745
745
  if (!state.available) return null;
746
746
  const title = props.t(props.titleKey);
747
+ const description = props.t(props.descriptionKey);
747
748
  const blocked = !state.dirty || state.invalid || state.saving;
748
749
  const cardClass = open ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
749
750
  if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
@@ -760,10 +761,12 @@ window.__ModuleLoader__.load({
760
761
  className: settings_card_module_css_default.headText,
761
762
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
762
763
  className: settings_card_module_css_default.name,
764
+ title,
763
765
  children: title
764
766
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
765
767
  className: settings_card_module_css_default.description,
766
- children: props.t(props.descriptionKey)
768
+ title: description,
769
+ children: description
767
770
  })]
768
771
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
769
772
  width: "14",
@@ -801,14 +804,17 @@ window.__ModuleLoader__.load({
801
804
  className: settings_card_module_css_default.headText,
802
805
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
803
806
  className: settings_card_module_css_default.name,
807
+ title,
804
808
  children: title
805
809
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
806
810
  className: settings_card_module_css_default.description,
807
- children: props.t(props.descriptionKey)
811
+ title: description,
812
+ children: description
808
813
  })]
809
814
  }),
810
815
  state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
811
816
  className: settings_card_module_css_default.pending,
817
+ title: props.t("settings.unsaved"),
812
818
  children: props.t("settings.unsaved")
813
819
  }) : null,
814
820
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
@@ -963,8 +969,9 @@ window.__ModuleLoader__.load({
963
969
  }
964
970
  //#endregion
965
971
  //#region src/client/settings-form.ts
966
- /** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */
967
- function numberField(field) {
972
+ /** A whole- or decimal-number field. An empty draft clears the field; any other draft that is not a finite number within the constraints blocks the save. */
973
+ function numberField(field, constraints = {}) {
974
+ const { integer = false, min } = constraints;
968
975
  return {
969
976
  field,
970
977
  format: (value) => typeof value === "number" ? String(value) : "",
@@ -972,10 +979,13 @@ window.__ModuleLoader__.load({
972
979
  const trimmed = text.trim();
973
980
  if (trimmed === "") return { kind: "clear" };
974
981
  const parsed = Number(trimmed);
975
- return Number.isFinite(parsed) ? {
982
+ if (!Number.isFinite(parsed)) return void 0;
983
+ if (integer && !Number.isInteger(parsed)) return void 0;
984
+ if (min !== void 0 && parsed < min) return void 0;
985
+ return {
976
986
  kind: "set",
977
987
  value: parsed
978
- } : void 0;
988
+ };
979
989
  }
980
990
  };
981
991
  }
@@ -999,11 +1009,13 @@ window.__ModuleLoader__.load({
999
1009
  field,
1000
1010
  format: (value) => typeof value === "boolean" ? String(value) : "",
1001
1011
  parse: (text) => {
1002
- if (text === "true") return {
1012
+ const trimmed = text.trim();
1013
+ if (trimmed === "") return { kind: "clear" };
1014
+ if (trimmed === "true") return {
1003
1015
  kind: "set",
1004
1016
  value: true
1005
1017
  };
1006
- if (text === "false") return {
1018
+ if (trimmed === "false") return {
1007
1019
  kind: "set",
1008
1020
  value: false
1009
1021
  };
@@ -1105,12 +1117,13 @@ window.__ModuleLoader__.load({
1105
1117
  const plan = this.plan();
1106
1118
  const writes = plan.flatMap((item) => item.run === void 0 ? [] : [item.run]);
1107
1119
  if (plan.length === 0 || this.saving || writes.length !== plan.length) return;
1120
+ const fields = new Set(plan.map((item) => item.field));
1108
1121
  this.saving = true;
1109
1122
  this.failed = false;
1110
1123
  this.publish();
1111
1124
  let landed = true;
1112
1125
  for (const write of writes) landed = await write() && landed;
1113
- if (landed) this.staged.clear();
1126
+ if (landed) for (const field of fields) this.staged.delete(field);
1114
1127
  this.saving = false;
1115
1128
  this.failed = !landed;
1116
1129
  this.publish();
@@ -1472,7 +1485,7 @@ window.__ModuleLoader__.load({
1472
1485
  setName: (name) => petFetch("/api/pet/set-name", { name })
1473
1486
  };
1474
1487
  /** Poll interval for the host snapshot. */
1475
- const POLL_MS = 800;
1488
+ const POLL_MS = 2e3;
1476
1489
  /** Settings namespace the pet settings card edits (the Host plugin registers it). */
1477
1490
  const PET_SETTINGS_NS = "pet";
1478
1491
  /** Required services. */