@linxin666/dsh-remote-web-ui 0.1.14 → 0.1.16

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 (45) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +9 -5
  3. package/README.zh.md +1 -1
  4. package/lib/client.js +84 -23
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +173 -18
  7. package/lib/mobile.js +44 -25
  8. package/lib/mobile.js.map +1 -1
  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 +30 -8
  12. package/lib/types/client/UpdatePanel.js +2 -0
  13. package/lib/types/client/locales.d.ts +4 -0
  14. package/lib/types/client/locales.d.ts.map +1 -1
  15. package/lib/types/client/locales.js +4 -0
  16. package/lib/types/client/settings-form.d.ts +76 -5
  17. package/lib/types/client/settings-form.d.ts.map +1 -1
  18. package/lib/types/client/settings-form.js +97 -20
  19. package/lib/types/index.d.ts.map +1 -1
  20. package/lib/types/index.js +36 -10
  21. package/lib/types/mobile/mux.d.ts +13 -4
  22. package/lib/types/mobile/mux.d.ts.map +1 -1
  23. package/lib/types/mobile/mux.js +50 -30
  24. package/lib/types/mobile/mux.test.js +75 -6
  25. package/lib/types/mobile-api.d.ts +2 -0
  26. package/lib/types/mobile-api.d.ts.map +1 -1
  27. package/lib/types/mobile-api.js +4 -1
  28. package/lib/types/routes.d.ts +17 -0
  29. package/lib/types/routes.d.ts.map +1 -1
  30. package/lib/types/routes.js +49 -10
  31. package/lib/types/update.d.ts +44 -4
  32. package/lib/types/update.d.ts.map +1 -1
  33. package/lib/types/update.js +118 -8
  34. package/package.json +1 -1
  35. package/src/client/PluginSettingsCard.tsx +90 -18
  36. package/src/client/UpdatePanel.tsx +2 -0
  37. package/src/client/locales.ts +4 -0
  38. package/src/client/settings-card.module.css +9 -0
  39. package/src/client/settings-form.ts +153 -19
  40. package/src/index.ts +34 -10
  41. package/src/mobile/mux.test.ts +84 -6
  42. package/src/mobile/mux.ts +46 -25
  43. package/src/mobile-api.ts +6 -1
  44. package/src/routes.ts +51 -10
  45. package/src/update.ts +128 -9
@@ -1,22 +1,27 @@
1
1
  /**
2
- * Shared chrome for the plugin settings card: a disclosure header naming the
3
- * plugin and what its settings govern, the controls inside, and the save that
4
- * writes them. Renders nothing while the namespace is unavailable — a
2
+ * Family-shared chrome for plugin settings cards: a disclosure header naming
3
+ * the plugin and what its settings govern, the controls inside, and the save
4
+ * that writes them. Renders nothing while the namespace is unavailable — a
5
5
  * deployment that does not compose the owning plugin should show no trace of
6
- * it. Mirrors the official ui-plugin-config PluginCard in a self-contained
7
- * slice (this package must not depend on a sibling UI package).
6
+ * it. Inlined into each consumer's client bundle; mirrors the official
7
+ * ui-plugin-config PluginCard in a self-contained slice.
8
8
  */
9
9
  import { type ReactNode } from 'react';
10
10
  import type { CardShell } from './settings-form.ts';
11
- import type { SettingsCardKey } from './locales.ts';
11
+ /** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
12
+ export declare const CARD_COPY_KEYS: readonly ["settings.collapse", "settings.expand", "settings.notExposed", "settings.unsaved", "settings.readOnly", "settings.saveFailed", "settings.discard", "settings.save", "settings.saving"];
13
+ /** Copy key the card chrome itself reads. */
14
+ export type CardCopyKey = (typeof CARD_COPY_KEYS)[number];
15
+ /** Key domain for the plugin's own copy (inferred per consumer). */
16
+ export type SettingsCardKey<TKey extends string = string> = TKey | CardCopyKey;
12
17
  /** Card chrome shared by every plugin settings card. */
13
- export interface PluginSettingsCardProps {
18
+ export interface PluginSettingsCardProps<TKey extends string = string> {
14
19
  /** Locale reader for this card's copy. */
15
- t: (key: SettingsCardKey) => string;
20
+ t: (key: SettingsCardKey<TKey>, params?: Record<string, string | number>) => string;
16
21
  /** Locale key of the plugin's name. */
17
- titleKey: SettingsCardKey;
22
+ titleKey: TKey;
18
23
  /** Locale key of the line describing what this plugin's settings govern. */
19
- descriptionKey: SettingsCardKey;
24
+ descriptionKey: TKey;
20
25
  /** The card's form state: availability, writability, and what a save would do. */
21
26
  state: CardShell;
22
27
  /** Write every staged edit. */
@@ -31,7 +36,7 @@ export interface PluginSettingsCardProps {
31
36
  * @param props - the plugin's copy keys, its form state, and its controls.
32
37
  * @returns the card, or nothing while the namespace is still loading.
33
38
  */
34
- export declare function PluginSettingsCard(props: PluginSettingsCardProps): import("react").JSX.Element | null;
39
+ export declare function PluginSettingsCard<TKey extends string = string>(props: PluginSettingsCardProps<TKey>): import("react").JSX.Element | null;
35
40
  /** Props every field control needs regardless of its value type. */
36
41
  export interface FieldProps {
37
42
  /** Stable id associating the label with its control. */
@@ -75,4 +80,14 @@ export declare function BooleanField(props: FieldProps & {
75
80
  /** Copy for the off option. */
76
81
  offLabel: string;
77
82
  }): import("react").JSX.Element;
83
+ /** A staged enumerated field rendered as a select. */
84
+ export declare function ChoiceField(props: FieldProps & {
85
+ /** Copy for the inherit option (draft text is the empty string). */
86
+ inheritLabel: string;
87
+ /** Choices rendered in order; `value` is the draft/stored text. */
88
+ choices: ReadonlyArray<{
89
+ value: string;
90
+ label: string;
91
+ }>;
92
+ }): import("react").JSX.Element;
78
93
  //# sourceMappingURL=PluginSettingsCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAGnD,wDAAwD;AACxD,MAAM,WAAW,uBAAuB;IACtC,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,CAAA;IACnC,uCAAuC;IACvC,QAAQ,EAAE,eAAe,CAAA;IACzB,4EAA4E;IAC5E,cAAc,EAAE,eAAe,CAAA;IAC/B,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,sCA0GhE;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,oEAAoE;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,+BAqCA;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAC/C,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,+BAmCA"}
1
+ {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,oGAAoG;AACpG,eAAO,MAAM,cAAc,kMAUjB,CAAA;AAEV,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,GAAG,WAAW,CAAA;AAE9E,wDAAwD;AACxD,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;IACnF,uCAAuC;IACvC,QAAQ,EAAE,IAAI,CAAA;IACd,4EAA4E;IAC5E,cAAc,EAAE,IAAI,CAAA;IACpB,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,uBAAuB,CAAC,IAAI,CAAC,sCAiHpG;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,oEAAoE;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,+BAqCA;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAC/C,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,+BAmCA;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG;IAC9C,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,OAAO,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACzD,+BAsCA"}
@@ -1,14 +1,27 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Generated by scripts/sync-shared.mjs from shared/client/settings/PluginSettingsCard.tsx. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
3
  /**
3
- * Shared chrome for the plugin settings card: a disclosure header naming the
4
- * plugin and what its settings govern, the controls inside, and the save that
5
- * writes them. Renders nothing while the namespace is unavailable — a
4
+ * Family-shared chrome for plugin settings cards: a disclosure header naming
5
+ * the plugin and what its settings govern, the controls inside, and the save
6
+ * that writes them. Renders nothing while the namespace is unavailable — a
6
7
  * deployment that does not compose the owning plugin should show no trace of
7
- * it. Mirrors the official ui-plugin-config PluginCard in a self-contained
8
- * slice (this package must not depend on a sibling UI package).
8
+ * it. Inlined into each consumer's client bundle; mirrors the official
9
+ * ui-plugin-config PluginCard in a self-contained slice.
9
10
  */
10
11
  import { useState } from 'react';
11
12
  import css from './settings-card.module.css';
13
+ /** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
14
+ export const CARD_COPY_KEYS = [
15
+ 'settings.collapse',
16
+ 'settings.expand',
17
+ 'settings.notExposed',
18
+ 'settings.unsaved',
19
+ 'settings.readOnly',
20
+ 'settings.saveFailed',
21
+ 'settings.discard',
22
+ 'settings.save',
23
+ 'settings.saving',
24
+ ];
12
25
  /**
13
26
  * Render one plugin settings card.
14
27
  * @param props - the plugin's copy keys, its form state, and its controls.
@@ -20,6 +33,7 @@ export function PluginSettingsCard(props) {
20
33
  if (!state.available)
21
34
  return null;
22
35
  const title = props.t(props.titleKey);
36
+ const description = props.t(props.descriptionKey);
23
37
  const blocked = !state.dirty || state.invalid || state.saving;
24
38
  const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card;
25
39
  // The namespace exists but the Host does not serve it to this client (the
@@ -27,12 +41,14 @@ export function PluginSettingsCard(props) {
27
41
  // that explains the gap instead of vanishing, so a missing card never
28
42
  // reads as a missing plugin.
29
43
  if (!state.exposed) {
30
- return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, children: title }), _jsx("span", { className: css.description, children: props.t(props.descriptionKey) })] }), _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
44
+ return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
31
45
  ? (_jsx("div", { className: css.body, children: _jsx("p", { className: css.notExposed, role: "status", children: props.t('settings.notExposed') }) }))
32
46
  : null] }));
33
47
  }
34
- return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, children: title }), _jsx("span", { className: css.description, children: props.t(props.descriptionKey) })] }), state.dirty ? _jsx("span", { className: css.pending, children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
35
- ? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed ? _jsx("p", { className: css.failed, role: "status", children: props.t('settings.saveFailed') }) : null, _jsx("button", { type: "button", className: css.discard, disabled: !state.dirty || state.saving, onClick: props.onDiscard, children: props.t('settings.discard') }), _jsx("button", { type: "button", className: css.save, disabled: blocked, onClick: props.onSave, children: props.t(!state.saving ? 'settings.save' : 'settings.saving') })] })] }))
48
+ return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
49
+ ? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed
50
+ ? (_jsxs("p", { className: css.failed, role: "status", children: [props.t('settings.saveFailed'), state.failedReason ? ' - ' + state.failedReason : ''] }))
51
+ : null, _jsx("button", { type: "button", className: css.discard, disabled: !state.dirty || state.saving, onClick: props.onDiscard, children: props.t('settings.discard') }), _jsx("button", { type: "button", className: css.save, disabled: blocked, onClick: props.onSave, children: props.t(!state.saving ? 'settings.save' : 'settings.saving') })] })] }))
36
52
  : null] }));
37
53
  }
38
54
  /** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */
@@ -47,3 +63,9 @@ export function BooleanField(props) {
47
63
  ? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
48
64
  : null] }), _jsxs("select", { id: props.id, className: css.select, value: props.text, disabled: props.disabled, onChange: (event) => { props.onEdit(event.target.value); }, children: [_jsx("option", { value: "", children: props.inheritLabel }), _jsx("option", { value: "true", children: props.onLabel }), _jsx("option", { value: "false", children: props.offLabel })] }), _jsx("p", { className: css.hint, children: props.hint })] }));
49
65
  }
66
+ /** A staged enumerated field rendered as a select. */
67
+ export function ChoiceField(props) {
68
+ return (_jsxs("div", { className: css.field, children: [_jsxs("div", { className: css.head, children: [_jsx("label", { className: css.label, htmlFor: props.id, children: props.label }), props.overridden
69
+ ? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
70
+ : null] }), _jsxs("select", { id: props.id, className: css.select, value: props.text, disabled: props.disabled, onChange: (event) => { props.onEdit(event.target.value); }, children: [_jsx("option", { value: "", children: props.inheritLabel }), props.choices.map(choice => (_jsx("option", { value: choice.value, children: choice.label }, choice.value)))] }), _jsx("p", { className: props.invalid ? css.invalid : css.hint, children: props.invalid ? props.invalidLabel : props.hint })] }));
71
+ }
@@ -77,6 +77,8 @@ function errorMessageOf(t, result) {
77
77
  case "not-found": return t("update.error.notFound");
78
78
  case "link": return t("update.error.link");
79
79
  case "pnpm-failed": return t("update.error.pnpmFailed", { code: String(result.exitCode ?? "?") });
80
+ case "stale": return t("update.error.stale");
81
+ case "verify-failed": return t("update.error.verifyFailed");
80
82
  default: return result.error ?? t("update.error.unknown");
81
83
  }
82
84
  }
@@ -99,6 +99,8 @@ export declare const zh: {
99
99
  'update.error.notFound': string;
100
100
  'update.error.link': string;
101
101
  'update.error.pnpmFailed': string;
102
+ 'update.error.stale': string;
103
+ 'update.error.verifyFailed': string;
102
104
  'update.error.unknown': string;
103
105
  };
104
106
  /** The remote namespace key union. */
@@ -205,6 +207,8 @@ export declare const en: {
205
207
  'update.error.notFound': string;
206
208
  'update.error.link': string;
207
209
  'update.error.pnpmFailed': string;
210
+ 'update.error.stale': string;
211
+ 'update.error.verifyFailed': string;
208
212
  'update.error.unknown': string;
209
213
  };
210
214
  //# sourceMappingURL=locales.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsGmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsGsB,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwGmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwGsB,CAAA"}
@@ -101,6 +101,8 @@ export const zh = {
101
101
  'update.error.notFound': '未找到 dsh-web-ui 聚合包安装,无法更新。',
102
102
  'update.error.link': '当前为本地开发模式(链接安装),无法自动更新。',
103
103
  'update.error.pnpmFailed': '更新执行失败(pnpm 退出码 {code}),详见下方输出。',
104
+ 'update.error.stale': '更新命令已执行,但安装版本未变化。常见原因:pnpm 11 的 minimumReleaseAge 门禁会静默跳过发布不足 24 小时的新版本。请在 profile 目录(~/.dsh/profiles/<profile>)的 pnpm-workspace.yaml 中配置 minimumReleaseAgeExclude(例如 "@linxin666/*")或 minimumReleaseAge: 0 后重新点击更新。',
105
+ 'update.error.verifyFailed': '更新命令已执行,但无法核对安装后的版本(可能是 registry 探测失败或安装路径变化)。请查看下方输出,或稍后重试。',
104
106
  'update.error.unknown': '更新失败,请重试。',
105
107
  };
106
108
  /** English dictionary, checked complete against the zh key set. */
@@ -205,5 +207,7 @@ export const en = {
205
207
  'update.error.notFound': 'The dsh-web-ui aggregate package is not installed; nothing to update.',
206
208
  'update.error.link': 'Local development mode (link install) — automatic update is unavailable.',
207
209
  'update.error.pnpmFailed': 'The update failed (pnpm exited with code {code}); see the output below.',
210
+ 'update.error.stale': 'The update command ran but the installed versions did not change. Likely cause: the pnpm 11 minimumReleaseAge gate silently skips releases published less than 24 hours ago. Add minimumReleaseAgeExclude (e.g. "@linxin666/*") or set minimumReleaseAge: 0 in pnpm-workspace.yaml under the profile directory (~/.dsh/profiles/<profile>), then run the update again.',
211
+ 'update.error.verifyFailed': 'The update command ran but the installed versions could not be verified afterwards (registry probe failed or the install path changed). Inspect the output below, or retry later.',
208
212
  'update.error.unknown': 'Update failed; try again.',
209
213
  };
@@ -2,9 +2,9 @@
2
2
  * Staged form model behind the plugin settings card. A card stages what the
3
3
  * user types and writes it only when they save — the settings write is a
4
4
  * durable, revision-fenced document mutation, so staging keeps what is on
5
- * screen exactly what a save would store. Mirrors the official
6
- * ui-plugin-config card-store pattern in a self-contained slice: this
7
- * package must not depend on a sibling UI package.
5
+ * screen exactly what a save would store. Family-shared slice inlined into
6
+ * each plugin's client bundle; mirrors the official ui-plugin-config
7
+ * card-store pattern.
8
8
  */
9
9
  import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
10
  /** The write one field's staged text performs when the card is saved. */
@@ -18,6 +18,14 @@ export type FieldWrite = {
18
18
  export interface FieldSpec {
19
19
  /** Field name inside the namespace section. */
20
20
  field: string;
21
+ /**
22
+ * Whether the Host treats this field as a secret and redacts its value from
23
+ * the read-back (role('secret') in the section schema). Redacted secrets are
24
+ * never compared against the draft on save; the field lands when the scope
25
+ * reports the write succeeded (its secret-set marker under the bridge), so
26
+ * a successful secret save is not misreported as failed.
27
+ */
28
+ secret?: boolean;
21
29
  /** Render a stored value as draft text; the empty string when the section carries none. */
22
30
  format: (value: unknown) => string;
23
31
  /**
@@ -57,6 +65,12 @@ export interface CardShell {
57
65
  saving: boolean;
58
66
  /** Whether the last save did not land as staged; cleared by the next edit or save. */
59
67
  failed: boolean;
68
+ /**
69
+ * The rejection code/message the Host returned for the last failed save,
70
+ * surfaced next to the generic failure text. Undefined while no save has
71
+ * failed (or the failure carried no server reason).
72
+ */
73
+ failedReason?: string;
60
74
  }
61
75
  /** The write actions the card's slot entry injects. */
62
76
  export interface CardActions {
@@ -69,12 +83,59 @@ export interface CardActions {
69
83
  /** Drop every staged edit. */
70
84
  discard: () => void;
71
85
  }
72
- /** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */
73
- export declare function numberField(field: string): FieldSpec;
86
+ /** One durable write a batched settings scope performs. */
87
+ export interface BatchedWrite {
88
+ /** Field this entry writes. */
89
+ field: string;
90
+ /** set stores a value; unset drops the leaf. */
91
+ op: 'set' | 'unset';
92
+ /** Value for op set (absent for unset). */
93
+ value?: unknown;
94
+ }
95
+ /** Per-field outcome of one batched scope write. */
96
+ export interface BatchedFieldResult {
97
+ /** Field this entry writes. */
98
+ field: string;
99
+ /** Whether the Host accepted this field's write (per the read-back view). */
100
+ landed: boolean;
101
+ }
102
+ /**
103
+ * Result of a batched scope write. The bridge scope posts every planned write
104
+ * in one /mutate so the Host validate hook judges baseURL+model together; a
105
+ * batched refusal fails the whole save rather than per-field.
106
+ */
107
+ export interface BatchResult {
108
+ /** Whether the whole mutate was accepted. */
109
+ ok: boolean;
110
+ /** Per-field success, in the request order (always present when ok). */
111
+ fields: BatchedFieldResult[];
112
+ /** Host rejection code (mutate refused). */
113
+ code?: string;
114
+ /** Host rejection message (mutate refused). */
115
+ message?: string;
116
+ }
117
+ /** Constraints a numeric field's accepted drafts must satisfy, mirroring the host schema. */
118
+ export interface NumberConstraints {
119
+ /** The accepted value must be a whole number. */
120
+ integer?: boolean;
121
+ /** The accepted value must be at least this. */
122
+ min?: number;
123
+ }
124
+ /** 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. */
125
+ export declare function numberField(field: string, constraints?: NumberConstraints): FieldSpec;
74
126
  /** A free-text field. An empty draft clears the field. */
75
127
  export declare function textField(field: string): FieldSpec;
128
+ /**
129
+ * A free-text field the Host treats as a secret and redacts from the read-back
130
+ * (role('secret') in the section schema). The card still edits it like text,
131
+ * but a save never compares the redacted value back and relies on the scope
132
+ * reporting the write landed.
133
+ */
134
+ export declare function secretField(field: string): FieldSpec;
76
135
  /** A boolean field, edited through true/false draft text. */
77
136
  export declare function booleanField(field: string): FieldSpec;
137
+ /** An enumerated string field; only the listed choices are accepted. An empty draft clears the field. */
138
+ export declare function choiceField(field: string, choices: readonly string[]): FieldSpec;
78
139
  /**
79
140
  * Stages one card's edits over one settings namespace and writes them on save.
80
141
  *
@@ -90,6 +151,7 @@ export declare class CardForm<T> {
90
151
  private readonly listeners;
91
152
  private saving;
92
153
  private failed;
154
+ private failedReason;
93
155
  /** @param scope - the bound settings scope for this card's namespace. */
94
156
  constructor(scope: SettingsScope<T>, specs: FieldSpec[]);
95
157
  /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
@@ -102,9 +164,18 @@ export declare class CardForm<T> {
102
164
  actions(): CardActions;
103
165
  /**
104
166
  * Write every staged edit, then re-seed from what the Host accepted.
167
+ *
168
+ * When the scope carries the optional batch surface (the dsh-web-ui
169
+ * bridge scope), every planned write rides one mutation so cross-field
170
+ * validate hooks (baseURL+model) judge the batch as a unit instead of
171
+ * deadlocking on per-field writes. Otherwise the per-field loop runs.
172
+ * A field lands only when the Host reports it held the staged value; a
173
+ * landed field's draft is dropped, a failed one stays staged for the user.
105
174
  * @returns settlement after every write and the read-back.
106
175
  */
107
176
  save(): Promise<void>;
177
+ /** The scope's batch surface when it supports one; undefined conservatively otherwise. */
178
+ private batchedScope;
108
179
  /**
109
180
  * Every staged edit a save would write. An entry whose draft is not a value
110
181
  * its field accepts carries no write: the form is still dirty, and the save
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,16 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/client/settings/settings-form.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
1
2
  /**
2
3
  * Staged form model behind the plugin settings card. A card stages what the
3
4
  * user types and writes it only when they save — the settings write is a
4
5
  * durable, revision-fenced document mutation, so staging keeps what is on
5
- * screen exactly what a save would store. Mirrors the official
6
- * ui-plugin-config card-store pattern in a self-contained slice: this
7
- * package must not depend on a sibling UI package.
6
+ * screen exactly what a save would store. Family-shared slice inlined into
7
+ * each plugin's client bundle; mirrors the official ui-plugin-config
8
+ * card-store pattern.
8
9
  */
9
10
  import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
- /** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */
11
- export function numberField(field) {
11
+ /** 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. */
12
+ export function numberField(field, constraints = {}) {
13
+ const { integer = false, min } = constraints;
12
14
  return {
13
15
  field,
14
16
  format: value => typeof value === 'number' ? String(value) : '',
@@ -17,7 +19,13 @@ export function numberField(field) {
17
19
  if (trimmed === '')
18
20
  return { kind: 'clear' };
19
21
  const parsed = Number(trimmed);
20
- return Number.isFinite(parsed) ? { kind: 'set', value: parsed } : undefined;
22
+ if (!Number.isFinite(parsed))
23
+ return undefined;
24
+ if (integer && !Number.isInteger(parsed))
25
+ return undefined;
26
+ if (min !== undefined && parsed < min)
27
+ return undefined;
28
+ return { kind: 'set', value: parsed };
21
29
  },
22
30
  };
23
31
  }
@@ -32,20 +40,44 @@ export function textField(field) {
32
40
  },
33
41
  };
34
42
  }
43
+ /**
44
+ * A free-text field the Host treats as a secret and redacts from the read-back
45
+ * (role('secret') in the section schema). The card still edits it like text,
46
+ * but a save never compares the redacted value back and relies on the scope
47
+ * reporting the write landed.
48
+ */
49
+ export function secretField(field) {
50
+ return { ...textField(field), secret: true };
51
+ }
35
52
  /** A boolean field, edited through true/false draft text. */
36
53
  export function booleanField(field) {
37
54
  return {
38
55
  field,
39
56
  format: value => typeof value === 'boolean' ? String(value) : '',
40
57
  parse: (text) => {
41
- if (text === 'true')
58
+ const trimmed = text.trim();
59
+ if (trimmed === '')
60
+ return { kind: 'clear' };
61
+ if (trimmed === 'true')
42
62
  return { kind: 'set', value: true };
43
- if (text === 'false')
63
+ if (trimmed === 'false')
44
64
  return { kind: 'set', value: false };
45
65
  return undefined;
46
66
  },
47
67
  };
48
68
  }
69
+ /** An enumerated string field; only the listed choices are accepted. An empty draft clears the field. */
70
+ export function choiceField(field, choices) {
71
+ return {
72
+ field,
73
+ format: value => typeof value === 'string' && choices.includes(value) ? value : '',
74
+ parse: (text) => {
75
+ if (text === '')
76
+ return { kind: 'clear' };
77
+ return choices.includes(text) ? { kind: 'set', value: text } : undefined;
78
+ },
79
+ };
80
+ }
49
81
  /**
50
82
  * Stages one card's edits over one settings namespace and writes them on save.
51
83
  *
@@ -61,6 +93,7 @@ export class CardForm {
61
93
  listeners = new Set();
62
94
  saving = false;
63
95
  failed = false;
96
+ failedReason;
64
97
  /** @param scope - the bound settings scope for this card's namespace. */
65
98
  constructor(scope, specs) {
66
99
  this.scope = scope;
@@ -85,6 +118,7 @@ export class CardForm {
85
118
  invalid: plan.some(item => item.run === undefined),
86
119
  saving: this.saving,
87
120
  failed: this.failed,
121
+ ...this.failedReason === undefined ? {} : { failedReason: this.failedReason },
88
122
  };
89
123
  }
90
124
  /** Read one field's state from the effective section and its staged draft. */
@@ -114,32 +148,68 @@ export class CardForm {
114
148
  return;
115
149
  this.staged.clear();
116
150
  this.failed = false;
151
+ this.failedReason = undefined;
117
152
  this.publish();
118
153
  },
119
154
  };
120
155
  }
121
156
  /**
122
157
  * Write every staged edit, then re-seed from what the Host accepted.
158
+ *
159
+ * When the scope carries the optional batch surface (the dsh-web-ui
160
+ * bridge scope), every planned write rides one mutation so cross-field
161
+ * validate hooks (baseURL+model) judge the batch as a unit instead of
162
+ * deadlocking on per-field writes. Otherwise the per-field loop runs.
163
+ * A field lands only when the Host reports it held the staged value; a
164
+ * landed field's draft is dropped, a failed one stays staged for the user.
123
165
  * @returns settlement after every write and the read-back.
124
166
  */
125
167
  async save() {
126
168
  const plan = this.plan();
127
- const writes = plan.flatMap(item => item.run === undefined ? [] : [item.run]);
128
- if (plan.length === 0 || this.saving || writes.length !== plan.length)
169
+ const valid = plan.filter(item => item.run !== undefined);
170
+ if (plan.length === 0 || this.saving || valid.length !== plan.length)
129
171
  return;
172
+ const plannedWrites = valid.map(item => item.op);
173
+ // Snapshot the fields this save writes, so edits staged while it is in
174
+ // flight survive: only the staged keys this save actually wrote are cleared.
175
+ const fields = new Set(plan.map(item => item.field));
130
176
  this.saving = true;
131
177
  this.failed = false;
178
+ this.failedReason = undefined;
132
179
  this.publish();
133
- let landed = true;
134
- for (const write of writes) {
135
- landed = await write() && landed;
180
+ const landed = new Set();
181
+ const batch = this.batchedScope();
182
+ if (batch !== undefined) {
183
+ const result = await batch.mutate(plannedWrites);
184
+ if (result.ok) {
185
+ for (const field of result.fields) {
186
+ if (field.landed)
187
+ landed.add(field.field);
188
+ }
189
+ }
190
+ else {
191
+ this.failedReason = result.message;
192
+ }
193
+ }
194
+ else {
195
+ for (const item of valid) {
196
+ if (await item.run())
197
+ landed.add(item.field);
198
+ }
199
+ }
200
+ for (const field of fields) {
201
+ if (landed.has(field))
202
+ this.staged.delete(field);
136
203
  }
137
- if (landed)
138
- this.staged.clear();
139
204
  this.saving = false;
140
- this.failed = !landed;
205
+ this.failed = landed.size !== fields.size;
141
206
  this.publish();
142
207
  }
208
+ /** The scope's batch surface when it supports one; undefined conservatively otherwise. */
209
+ batchedScope() {
210
+ const candidate = this.scope;
211
+ return typeof candidate?.mutate === 'function' ? candidate : undefined;
212
+ }
143
213
  /**
144
214
  * Every staged edit a save would write. An entry whose draft is not a value
145
215
  * its field accepts carries no write: the form is still dirty, and the save
@@ -153,18 +223,18 @@ export class CardForm {
153
223
  const spec = this.specOf(field);
154
224
  if (staged.clear) {
155
225
  if (this.stored(field))
156
- plan.push({ field, run: () => this.clear(field) });
226
+ plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) });
157
227
  continue;
158
228
  }
159
229
  if (staged.text === spec.format(this.sectionValue(field)))
160
230
  continue;
161
231
  const write = spec.parse(staged.text);
162
232
  if (write === undefined)
163
- plan.push({ field, run: undefined });
233
+ plan.push({ field, op: { field, op: 'unset' }, run: undefined });
164
234
  else if (write.kind === 'clear')
165
- plan.push({ field, run: () => this.clear(field) });
235
+ plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) });
166
236
  else
167
- plan.push({ field, run: () => this.store(field, write.value) });
237
+ plan.push({ field, op: { field, op: 'set', value: write.value }, run: () => this.store(field, write.value) });
168
238
  }
169
239
  return plan;
170
240
  }
@@ -174,11 +244,18 @@ export class CardForm {
174
244
  }
175
245
  async store(field, value) {
176
246
  await this.scope.set(field, value);
247
+ // A redacted secret never appears in the user layer read-back; judging it
248
+ // by value would misreport a successful secret save as failed. The bridge
249
+ // reports secret writes through its secret-set markers (batch path); on
250
+ // the per-field path the scope resolved, so the write is landed.
251
+ if (this.specOf(field).secret)
252
+ return true;
177
253
  return this.userLayer()?.[field] === value;
178
254
  }
179
255
  stage(field, edit) {
180
256
  this.staged.set(field, edit);
181
257
  this.failed = false;
258
+ this.failedReason = undefined;
182
259
  this.publish();
183
260
  }
184
261
  specOf(field) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAmB3B,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA4B,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAU3B,CAAA;AAyBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CA0MzD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAmB3B,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA4B,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAU3B,CAAA;AAyBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAkOzD"}