@linxin666/dsh-pet 0.1.16 → 0.1.18

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.
@@ -1 +1 @@
1
- {"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAE1F,OAAO,EAAoD,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAE1J,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,0CAA0C;AAC1C,MAAM,WAAW,oBAAqB,SAAQ,SAAS;IACrD,4BAA4B;IAC5B,OAAO,EAAE,cAAc,CAAA;IACvB,qBAAqB;IACrB,OAAO,EAAE,cAAc,CAAA;IACvB,iBAAiB;IACjB,IAAI,EAAE,cAAc,CAAA;IACpB,mBAAmB;IACnB,KAAK,EAAE,cAAc,CAAA;IACrB,oBAAoB;IACpB,MAAM,EAAE,cAAc,CAAA;IACtB,oBAAoB;IACpB,KAAK,EAAE,cAAc,CAAA;IACrB,wEAAwE;IACxE,UAAU,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CACxD;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,KAAK,EAAE;QACL,iEAAiE;QACjE,eAAe,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAA;KACrD,CAAA;CACF;AAeD,2DAA2D;AAC3D,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAI3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAI;IAEpB,uEAAuE;gBAC3D,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC;IAa7C,0EAA0E;YAC5D,QAAQ;IAgBtB,OAAO,CAAC,UAAU;IAalB;;;OAGG;IACH,MAAM,IAAI,mBAAmB;CAG9B;AAED,0DAA0D;AAC1D,MAAM,MAAM,oBAAoB,GAC9B,YAAY,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,KAAK,CAAC,GAClB,UAAU,CAAC,mBAAmB,CAAC,CAAA;AAEnC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAsF1D"}
1
+ {"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAI1F,OAAO,EAAoD,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAG1J,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,0CAA0C;AAC1C,MAAM,WAAW,oBAAqB,SAAQ,SAAS;IACrD,4BAA4B;IAC5B,OAAO,EAAE,cAAc,CAAA;IACvB,qBAAqB;IACrB,OAAO,EAAE,cAAc,CAAA;IACvB,iBAAiB;IACjB,IAAI,EAAE,cAAc,CAAA;IACpB,mBAAmB;IACnB,KAAK,EAAE,cAAc,CAAA;IACrB,oBAAoB;IACpB,MAAM,EAAE,cAAc,CAAA;IACtB,oBAAoB;IACpB,KAAK,EAAE,cAAc,CAAA;IACrB,wEAAwE;IACxE,UAAU,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CACxD;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,KAAK,EAAE;QACL,iEAAiE;QACjE,eAAe,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAA;KACrD,CAAA;CACF;AAeD,2DAA2D;AAC3D,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAI3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAI;IAEpB,uEAAuE;gBAC3D,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC;IAa7C,0EAA0E;YAC5D,QAAQ;IAgBtB,OAAO,CAAC,UAAU;IAalB;;;OAGG;IACH,MAAM,IAAI,mBAAmB;CAG9B;AAED,0DAA0D;AAC1D,MAAM,MAAM,oBAAoB,GAC9B,WAAW,CAAC,KAAK,CAAC,GAChB,UAAU,CAAC,mBAAmB,CAAC,CAAA;AAEnC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAuF1D;AAED,8DAA8D;AAC9D,MAAM,MAAM,uBAAuB,GACjC,YAAY,CAAC,kBAAkB,CAAC,GAC9B,WAAW,CAAC,KAAK,CAAC,GAClB,UAAU,CAAC,mBAAmB,CAAC,CAAA;AAEnC,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAO5E"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PluginSettingsCard, ValueField, BooleanField, ChoiceField } from "./PluginSettingsCard.js";
3
3
  import { CardForm, booleanField, choiceField, numberField } from "./settings-form.js";
4
+ import sectionCss from './settings-section.module.css';
4
5
  /** Fetch the registry list (the same data the sprite renders from). */
5
6
  async function fetchPetChoices() {
6
7
  const response = await fetch('/api/pet/pets');
@@ -86,5 +87,10 @@ export function PetSettingsCard(props) {
86
87
  invalidLabel: t('settings.invalidNumber'),
87
88
  disabled,
88
89
  };
89
- return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", state: state, onSave: props.save, onDiscard: props.discard, children: [_jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } })] }));
90
+ return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", state: state, onSave: props.save, onDiscard: props.discard, alwaysOpen: true, children: [_jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } })] }));
91
+ }
92
+ /** Render the pet settings card as a first-level settings page. */
93
+ export function PetSettingsSection(props) {
94
+ const { t, usePetSettingsCard, save, discard, edit, resetField } = props;
95
+ return (_jsx("ul", { className: sectionCss.sectionList, children: _jsx(PetSettingsCard, { t: t, usePetSettingsCard: usePetSettingsCard, save: save, discard: discard, edit: edit, resetField: resetField }) }));
90
96
  }
@@ -28,6 +28,17 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
28
28
  onSave: () => void;
29
29
  /** Drop every staged edit. */
30
30
  onDiscard: () => void;
31
+ /**
32
+ * Render the controls expanded on arrival (still collapsible). Defaults to
33
+ * true: promoted first-level sections show their settings immediately.
34
+ */
35
+ defaultOpen?: boolean;
36
+ /**
37
+ * Render without the collapse affordance: a static header and an always
38
+ * visible body. Used by first-level settings sections whose nav entry
39
+ * already provides the selection.
40
+ */
41
+ alwaysOpen?: boolean;
31
42
  /** The plugin's controls. */
32
43
  children: ReactNode;
33
44
  }
@@ -1 +1 @@
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
+ {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"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"}
@@ -28,24 +28,30 @@ export const CARD_COPY_KEYS = [
28
28
  * @returns the card, or nothing while the namespace is still loading.
29
29
  */
30
30
  export function PluginSettingsCard(props) {
31
- const [open, setOpen] = useState(false);
32
- const { state } = props;
31
+ const [open, setOpen] = useState(props.defaultOpen ?? true);
32
+ const { state, alwaysOpen } = props;
33
33
  if (!state.available)
34
34
  return null;
35
35
  const title = props.t(props.titleKey);
36
36
  const description = props.t(props.descriptionKey);
37
37
  const blocked = !state.dirty || state.invalid || state.saving;
38
- const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card;
38
+ const expanded = alwaysOpen === true || open;
39
+ const cardClass = expanded ? `${css.cardOpen} ${css.card}` : css.card;
40
+ // With alwaysOpen the nav entry already provides the selection, so the
41
+ // header is a static title row instead of a disclosure button.
42
+ const header = alwaysOpen === true
43
+ ? (_jsxs("div", { className: css.headerStatic, 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] }))
44
+ : (_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" }) })] }));
39
45
  // The namespace exists but the Host does not serve it to this client (the
40
46
  // official settings allowlist omits third-party namespaces): show a card
41
47
  // that explains the gap instead of vanishing, so a missing card never
42
48
  // reads as a missing plugin.
43
49
  if (!state.exposed) {
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
50
+ return (_jsxs("li", { className: cardClass, children: [header, expanded
45
51
  ? (_jsx("div", { className: css.body, children: _jsx("p", { className: css.notExposed, role: "status", children: props.t('settings.notExposed') }) }))
46
52
  : null] }));
47
53
  }
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
54
+ return (_jsxs("li", { className: cardClass, children: [header, expanded
49
55
  ? (_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
56
  ? (_jsxs("p", { className: css.failed, role: "status", children: [props.t('settings.saveFailed'), state.failedReason ? ' - ' + state.failedReason : ''] }))
51
57
  : 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') })] })] }))
@@ -18,27 +18,8 @@ export type { PetInjected, PetDockEntryProps } from './PetDockEntry.tsx';
18
18
  export type { PetSpriteProps } from './PetSprite.tsx';
19
19
  export type { PetUiState, PetFeedback } from './pet-store.ts';
20
20
  export type { PetSettingsCardFace, PetSettingsCardState } from './PetSettingsCard.tsx';
21
+ export type { PetSettingsSectionProps } from './PetSettingsCard.tsx';
21
22
  export type { PetDefinition } from '../registry.ts';
22
- declare module '@deepseek-ai/dsh-client-ui-slots' {
23
- interface SlotMap {
24
- /**
25
- * The child slot the Web UI plugin group declares; this card registers
26
- * into the group instead of the top-level 'settings.plugin.item' list.
27
- * Spelled here with the same shape so this package can register without
28
- * depending on the sibling UI package.
29
- */
30
- 'web-ui.plugin.item': {
31
- kind: 'list';
32
- scope: 'root';
33
- owner: SettingsPluginItemOwnerProps;
34
- };
35
- }
36
- }
37
- /** Owner share of a plugin card (the group card supplies nothing). */
38
- export interface SettingsPluginItemOwnerProps {
39
- /** Marker field: card owner props are intentionally empty. */
40
- children?: never;
41
- }
42
23
  declare module '@deepseek-ai/cordis' {
43
24
  interface Context {
44
25
  /**
@@ -53,8 +34,8 @@ declare module '@deepseek-ai/cordis' {
53
34
  }
54
35
  /**
55
36
  * Client plugin body: register dictionaries, mount the global pet entry and
56
- * poll loop while the plugin is enabled, and seat the settings card in the
57
- * Web UI plugin group.
37
+ * poll loop while the plugin is enabled, and seat the settings card as a
38
+ * first-level settings section.
58
39
  * @param ctx - client root context.
59
40
  */
60
41
  export declare function apply(ctx: ClientContext): void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AA6D7G,yBAAyB;AACzB,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,sEAAsE;AACtE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAmL9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AA6D7G,yBAAyB;AACzB,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAoL9C"}
@@ -14,7 +14,7 @@ import { createElement } from 'react';
14
14
  import { createRoot } from 'react-dom/client';
15
15
  import { createPetStore } from "./pet-store.js";
16
16
  import { PetDockEntry } from "./PetDockEntry.js";
17
- import { PetSettingsCard, PetSettingsCardController } from "./PetSettingsCard.js";
17
+ import { PetSettingsSection, PetSettingsCardController } from "./PetSettingsCard.js";
18
18
  import { NS, en, zh, t } from "./locales.js";
19
19
  /** Same-origin JSON fetch helper (GET without body, POST with JSON body). */
20
20
  async function petFetch(path, body) {
@@ -48,8 +48,8 @@ const PET_SETTINGS_NS = 'pet';
48
48
  export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote'];
49
49
  /**
50
50
  * Client plugin body: register dictionaries, mount the global pet entry and
51
- * poll loop while the plugin is enabled, and seat the settings card in the
52
- * Web UI plugin group.
51
+ * poll loop while the plugin is enabled, and seat the settings card as a
52
+ * first-level settings section.
53
53
  * @param ctx - client root context.
54
54
  */
55
55
  export function apply(ctx) {
@@ -62,17 +62,18 @@ export function apply(ctx) {
62
62
  ? snapshot.value?.enabled ?? true
63
63
  : snapshot.status === 'unavailable';
64
64
  };
65
- // Plugin configuration card: one staged form over the 'pet' settings
66
- // namespace, contributed to the Web UI plugin group. The controller loads
65
+ // First-level settings section: one staged form over the 'pet' settings
66
+ // namespace, registered as a top-level settings page. The controller loads
67
67
  // the petId choices from the registry endpoint itself.
68
68
  const petSettings = new PetSettingsCardController(settingsScope);
69
- ctx.slots.inject('web-ui.plugin.item', () => ctx.slots.register({
70
- name: 'web-ui.plugin.item',
71
- id: 'pet-settings',
72
- order: 140,
73
- locale: NS,
69
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
70
+ name: 'settings.section',
71
+ id: 'pet',
72
+ order: 130,
73
+ label: () => ctx.locale.bind('pet')('settings.title'),
74
+ locale: 'pet',
74
75
  inject: () => petSettings.inject(),
75
- }, PetSettingsCard));
76
+ }, PetSettingsSection));
76
77
  // The global pet entry, its store, and the poll loop live while the plugin
77
78
  // is enabled; toggling the setting off hides the pet and stops polling.
78
79
  let disposeUi;
@@ -17,7 +17,7 @@ export const zh = {
17
17
  'pet.treats': '小鱼干 ×{n}',
18
18
  'pet.state.loading': '宠物正在赶来…',
19
19
  'pet.state.error': '宠物迷路了(连接失败)',
20
- // 插件设置卡片(settings.plugin.item 席位)。
20
+ // 一级设置页(settings.section 席位)。
21
21
  'settings.title': '宠物',
22
22
  'settings.description': '选择宠物并调整它的显示布局。',
23
23
  'settings.pet': '宠物',
@@ -61,7 +61,7 @@ export const en = {
61
61
  'pet.treats': 'Treats ×{n}',
62
62
  'pet.state.loading': 'The pet is on its way…',
63
63
  'pet.state.error': 'The pet is lost (connection failed)',
64
- // Plugin settings card (the `settings.plugin.item` seat).
64
+ // First-level settings section (the `settings.section` seat).
65
65
  'settings.title': 'Pet',
66
66
  'settings.description': 'Pick a pet and tune its display layout.',
67
67
  'settings.pet': 'Pet',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-pet",
3
3
  "description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
4
- "version": "0.1.16",
4
+ "version": "0.1.18",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -1,15 +1,20 @@
1
1
  /**
2
2
  * The pet settings card: pet selection plus display layout, bound to the
3
- * 'pet' settings namespace the host plugin registers. Registered into the
4
- * 'web-ui.plugin.item' slot the plugin-configuration section renders. The
5
- * petId choices come from the registry endpoint ('/api/pet/pets') — the same
6
- * list the sprite renders from — so the card carries no per-pet knowledge.
3
+ * 'pet' settings namespace the host plugin registers. Rendered as an
4
+ * always-open first-level settings page; the section wrapper below mounts it
5
+ * as the content of the top-level 'settings.section' nav entry. The petId
6
+ * choices come from the registry endpoint ('/api/pet/pets') — the same list
7
+ * the sprite renders from — so the card carries no per-pet knowledge.
7
8
  */
8
9
 
10
+ import type { ReactNode } from 'react'
9
11
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
10
12
  import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
13
+ // Type-only: pulls the settings-surface SlotMap merge (the 'settings.section' entry).
14
+ import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
11
15
  import { PluginSettingsCard, ValueField, BooleanField, ChoiceField } from './PluginSettingsCard.tsx'
12
16
  import { CardForm, booleanField, choiceField, numberField, type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts'
17
+ import sectionCss from './settings-section.module.css'
13
18
 
14
19
  /** The pet's settings fields this card edits (the namespace's full schema). */
15
20
  export interface PetSettings {
@@ -133,8 +138,7 @@ export class PetSettingsCardController {
133
138
 
134
139
  /** Props the renderer binds for the pet settings card. */
135
140
  export type PetSettingsCardProps =
136
- PropsRuntime<'web-ui.plugin.item'>
137
- & PropsLocale<'pet'>
141
+ PropsLocale<'pet'>
138
142
  & InjectFace<PetSettingsCardFace>
139
143
 
140
144
  /**
@@ -160,6 +164,7 @@ export function PetSettingsCard(props: PetSettingsCardProps) {
160
164
  state={state}
161
165
  onSave={props.save}
162
166
  onDiscard={props.discard}
167
+ alwaysOpen
163
168
  >
164
169
  <BooleanField
165
170
  id="settings-pet-enabled"
@@ -229,3 +234,19 @@ export function PetSettingsCard(props: PetSettingsCardProps) {
229
234
  </PluginSettingsCard>
230
235
  )
231
236
  }
237
+
238
+ /** Props the settings section binds for the pet card page. */
239
+ export type PetSettingsSectionProps =
240
+ PropsRuntime<'settings.section'>
241
+ & PropsLocale<'pet'>
242
+ & InjectFace<PetSettingsCardFace>
243
+
244
+ /** Render the pet settings card as a first-level settings page. */
245
+ export function PetSettingsSection(props: PetSettingsSectionProps): ReactNode {
246
+ const { t, usePetSettingsCard, save, discard, edit, resetField } = props
247
+ return (
248
+ <ul className={sectionCss.sectionList}>
249
+ <PetSettingsCard t={t} usePetSettingsCard={usePetSettingsCard} save={save} discard={discard} edit={edit} resetField={resetField} />
250
+ </ul>
251
+ )
252
+ }
@@ -45,6 +45,17 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
45
45
  onSave: () => void
46
46
  /** Drop every staged edit. */
47
47
  onDiscard: () => void
48
+ /**
49
+ * Render the controls expanded on arrival (still collapsible). Defaults to
50
+ * true: promoted first-level sections show their settings immediately.
51
+ */
52
+ defaultOpen?: boolean
53
+ /**
54
+ * Render without the collapse affordance: a static header and an always
55
+ * visible body. Used by first-level settings sections whose nav entry
56
+ * already provides the selection.
57
+ */
58
+ alwaysOpen?: boolean
48
59
  /** The plugin's controls. */
49
60
  children: ReactNode
50
61
  }
@@ -55,57 +66,27 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
55
66
  * @returns the card, or nothing while the namespace is still loading.
56
67
  */
57
68
  export function PluginSettingsCard<TKey extends string = string>(props: PluginSettingsCardProps<TKey>) {
58
- const [open, setOpen] = useState(false)
59
- const { state } = props
69
+ const [open, setOpen] = useState(props.defaultOpen ?? true)
70
+ const { state, alwaysOpen } = props
60
71
  if (!state.available) return null
61
72
  const title = props.t(props.titleKey)
62
73
  const description = props.t(props.descriptionKey)
63
74
  const blocked = !state.dirty || state.invalid || state.saving
64
- const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card
65
- // The namespace exists but the Host does not serve it to this client (the
66
- // official settings allowlist omits third-party namespaces): show a card
67
- // that explains the gap instead of vanishing, so a missing card never
68
- // reads as a missing plugin.
69
- if (!state.exposed) {
70
- return (
71
- <li className={cardClass}>
72
- <button
73
- type="button"
74
- className={css.header}
75
- aria-expanded={open}
76
- aria-label={`${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}
77
- onClick={() => { setOpen(!open) }}
78
- >
79
- <span className={css.headText}>
80
- <span className={css.name} title={title}>{title}</span>
81
- <span className={css.description} title={description}>{description}</span>
82
- </span>
83
- <svg
84
- width="14"
85
- height="14"
86
- viewBox="0 0 14 14"
87
- fill="none"
88
- xmlns="http://www.w3.org/2000/svg"
89
- className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}
90
- >
91
- <path
92
- 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"
93
- fill="currentColor"
94
- />
95
- </svg>
96
- </button>
97
- {open
98
- ? (
99
- <div className={css.body}>
100
- <p className={css.notExposed} role="status">{props.t('settings.notExposed')}</p>
101
- </div>
102
- )
103
- : null}
104
- </li>
75
+ const expanded = alwaysOpen === true || open
76
+ const cardClass = expanded ? `${css.cardOpen} ${css.card}` : css.card
77
+ // With alwaysOpen the nav entry already provides the selection, so the
78
+ // header is a static title row instead of a disclosure button.
79
+ const header = alwaysOpen === true
80
+ ? (
81
+ <div className={css.headerStatic}>
82
+ <span className={css.headText}>
83
+ <span className={css.name} title={title}>{title}</span>
84
+ <span className={css.description} title={description}>{description}</span>
85
+ </span>
86
+ {state.dirty ? <span className={css.pending} title={props.t('settings.unsaved')}>{props.t('settings.unsaved')}</span> : null}
87
+ </div>
105
88
  )
106
- }
107
- return (
108
- <li className={cardClass}>
89
+ : (
109
90
  <button
110
91
  type="button"
111
92
  className={css.header}
@@ -132,7 +113,29 @@ export function PluginSettingsCard<TKey extends string = string>(props: PluginSe
132
113
  />
133
114
  </svg>
134
115
  </button>
135
- {open
116
+ )
117
+ // The namespace exists but the Host does not serve it to this client (the
118
+ // official settings allowlist omits third-party namespaces): show a card
119
+ // that explains the gap instead of vanishing, so a missing card never
120
+ // reads as a missing plugin.
121
+ if (!state.exposed) {
122
+ return (
123
+ <li className={cardClass}>
124
+ {header}
125
+ {expanded
126
+ ? (
127
+ <div className={css.body}>
128
+ <p className={css.notExposed} role="status">{props.t('settings.notExposed')}</p>
129
+ </div>
130
+ )
131
+ : null}
132
+ </li>
133
+ )
134
+ }
135
+ return (
136
+ <li className={cardClass}>
137
+ {header}
138
+ {expanded
136
139
  ? (
137
140
  <div className={css.body}>
138
141
  {!state.writable ? <p className={css.readOnly} role="status">{props.t('settings.readOnly')}</p> : null}
@@ -26,7 +26,7 @@ import { createElement } from 'react'
26
26
  import { createRoot } from 'react-dom/client'
27
27
  import { createPetStore, type PetStoreInstance } from './pet-store.ts'
28
28
  import { PetDockEntry, type PetInjected } from './PetDockEntry.tsx'
29
- import { PetSettingsCard, PetSettingsCardController, type PetSettings } from './PetSettingsCard.tsx'
29
+ import { PetSettingsSection, PetSettingsCardController, type PetSettings } from './PetSettingsCard.tsx'
30
30
  import { NS, en, zh, t } from './locales.ts'
31
31
 
32
32
  /** The host pet API as the browser sees it (same-origin JSON endpoints). */
@@ -80,26 +80,9 @@ export type { PetInjected, PetDockEntryProps } from './PetDockEntry.tsx'
80
80
  export type { PetSpriteProps } from './PetSprite.tsx'
81
81
  export type { PetUiState, PetFeedback } from './pet-store.ts'
82
82
  export type { PetSettingsCardFace, PetSettingsCardState } from './PetSettingsCard.tsx'
83
+ export type { PetSettingsSectionProps } from './PetSettingsCard.tsx'
83
84
  export type { PetDefinition } from '../registry.ts'
84
85
 
85
- declare module '@deepseek-ai/dsh-client-ui-slots' {
86
- interface SlotMap {
87
- /**
88
- * The child slot the Web UI plugin group declares; this card registers
89
- * into the group instead of the top-level 'settings.plugin.item' list.
90
- * Spelled here with the same shape so this package can register without
91
- * depending on the sibling UI package.
92
- */
93
- 'web-ui.plugin.item': { kind: 'list'; scope: 'root'; owner: SettingsPluginItemOwnerProps }
94
- }
95
- }
96
-
97
- /** Owner share of a plugin card (the group card supplies nothing). */
98
- export interface SettingsPluginItemOwnerProps {
99
- /** Marker field: card owner props are intentionally empty. */
100
- children?: never
101
- }
102
-
103
86
  declare module '@deepseek-ai/cordis' {
104
87
  interface Context {
105
88
  /**
@@ -113,8 +96,8 @@ declare module '@deepseek-ai/cordis' {
113
96
 
114
97
  /**
115
98
  * Client plugin body: register dictionaries, mount the global pet entry and
116
- * poll loop while the plugin is enabled, and seat the settings card in the
117
- * Web UI plugin group.
99
+ * poll loop while the plugin is enabled, and seat the settings card as a
100
+ * first-level settings section.
118
101
  * @param ctx - client root context.
119
102
  */
120
103
  export function apply(ctx: ClientContext): void {
@@ -129,17 +112,18 @@ export function apply(ctx: ClientContext): void {
129
112
  : snapshot.status === 'unavailable'
130
113
  }
131
114
 
132
- // Plugin configuration card: one staged form over the 'pet' settings
133
- // namespace, contributed to the Web UI plugin group. The controller loads
115
+ // First-level settings section: one staged form over the 'pet' settings
116
+ // namespace, registered as a top-level settings page. The controller loads
134
117
  // the petId choices from the registry endpoint itself.
135
118
  const petSettings = new PetSettingsCardController(settingsScope)
136
- ctx.slots.inject('web-ui.plugin.item', () => ctx.slots.register({
137
- name: 'web-ui.plugin.item',
138
- id: 'pet-settings',
139
- order: 140,
140
- locale: NS,
119
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
120
+ name: 'settings.section',
121
+ id: 'pet',
122
+ order: 130,
123
+ label: () => ctx.locale.bind('pet')('settings.title'),
124
+ locale: 'pet',
141
125
  inject: () => petSettings.inject(),
142
- }, PetSettingsCard))
126
+ }, PetSettingsSection))
143
127
 
144
128
  // The global pet entry, its store, and the poll loop live while the plugin
145
129
  // is enabled; toggling the setting off hides the pet and stops polling.
@@ -19,7 +19,7 @@ export const zh = {
19
19
  'pet.treats': '小鱼干 ×{n}',
20
20
  'pet.state.loading': '宠物正在赶来…',
21
21
  'pet.state.error': '宠物迷路了(连接失败)',
22
- // 插件设置卡片(settings.plugin.item 席位)。
22
+ // 一级设置页(settings.section 席位)。
23
23
  'settings.title': '宠物',
24
24
  'settings.description': '选择宠物并调整它的显示布局。',
25
25
  'settings.pet': '宠物',
@@ -64,7 +64,7 @@ export const en = {
64
64
  'pet.treats': 'Treats ×{n}',
65
65
  'pet.state.loading': 'The pet is on its way…',
66
66
  'pet.state.error': 'The pet is lost (connection failed)',
67
- // Plugin settings card (the `settings.plugin.item` seat).
67
+ // First-level settings section (the `settings.section` seat).
68
68
  'settings.title': 'Pet',
69
69
  'settings.description': 'Pick a pet and tune its display layout.',
70
70
  'settings.pet': 'Pet',
@@ -42,6 +42,15 @@
42
42
  outline-offset: -2px;
43
43
  }
44
44
 
45
+ .headerStatic {
46
+ width: 100%;
47
+ border-radius: 12px;
48
+ align-items: center;
49
+ gap: 12px;
50
+ padding: 14px 16px;
51
+ display: flex;
52
+ }
53
+
45
54
  .headText {
46
55
  flex-direction: column;
47
56
  flex: 1;
@@ -0,0 +1,5 @@
1
+ .sectionList {
2
+ list-style: none;
3
+ margin: 0;
4
+ padding: 0;
5
+ }