@ampless/admin 1.0.0-alpha.85 → 1.0.0-beta.101

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 (43) hide show
  1. package/README.ja.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/api/index.d.ts +1 -1
  4. package/dist/api/index.js +4 -2
  5. package/dist/{chunk-SQ6LFPXH.js → chunk-2TBGPB5H.js} +2 -2
  6. package/dist/{chunk-K44QF3MV.js → chunk-4OFRX6QX.js} +1 -1
  7. package/dist/{chunk-HANIMWMN.js → chunk-4WYMEUYU.js} +301 -222
  8. package/dist/chunk-7HSWOQWV.js +172 -0
  9. package/dist/{chunk-J6R356H3.js → chunk-7OZ23MLD.js} +1 -1
  10. package/dist/{chunk-OFPNBZKP.js → chunk-7W7LAQXY.js} +76 -8
  11. package/dist/{chunk-HN3UFHP7.js → chunk-ANME5KDN.js} +1 -1
  12. package/dist/{chunk-GGHN2RKQ.js → chunk-E5VF3Y65.js} +69 -3
  13. package/dist/{chunk-7LT6YPIT.js → chunk-JLCTF5HY.js} +8 -2
  14. package/dist/{chunk-R3TEJ32M.js → chunk-LXY3BRKQ.js} +2 -2
  15. package/dist/{chunk-MKWYNK4J.js → chunk-NHMG2MRJ.js} +2 -2
  16. package/dist/{chunk-AYESATAM.js → chunk-OPVLQOOB.js} +44 -13
  17. package/dist/{chunk-SMI7Y77B.js → chunk-ORXOMW7M.js} +265 -190
  18. package/dist/{chunk-W4VHP2JW.js → chunk-SYAEM5DM.js} +1 -1
  19. package/dist/{chunk-7NQERXNY.js → chunk-TJZ4FTU6.js} +1 -1
  20. package/dist/{chunk-CFIOUXVE.js → chunk-V5XORHKX.js} +1 -1
  21. package/dist/chunk-ZDIDJCSU.js +40 -0
  22. package/dist/components/account-view.d.ts +8 -0
  23. package/dist/components/account-view.js +10 -0
  24. package/dist/components/admin-dashboard.js +3 -3
  25. package/dist/components/edit-post-view.js +5 -5
  26. package/dist/components/index.d.ts +4 -2
  27. package/dist/components/index.js +7 -7
  28. package/dist/components/login-view.d.ts +3 -1
  29. package/dist/components/login-view.js +4 -3
  30. package/dist/components/mcp-tokens-view.js +3 -3
  31. package/dist/components/media-view.js +5 -5
  32. package/dist/components/new-post-view.js +5 -5
  33. package/dist/components/plugin-settings-form.d.ts +2 -10
  34. package/dist/components/plugin-settings-form.js +5 -7
  35. package/dist/components/posts-list-view.js +3 -3
  36. package/dist/components/users-list-view.js +3 -3
  37. package/dist/{i18n-DA-aakRX.d.ts → i18n-Bd8q9aTl.d.ts} +108 -5
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.js +1 -1
  40. package/dist/metafile-esm.json +1 -1
  41. package/dist/pages/index.d.ts +21 -4
  42. package/dist/pages/index.js +54 -24
  43. package/package.json +5 -6
@@ -5,14 +5,14 @@ import {
5
5
  import {
6
6
  useLocale,
7
7
  useT
8
- } from "./chunk-K44QF3MV.js";
8
+ } from "./chunk-4OFRX6QX.js";
9
9
 
10
10
  // src/components/plugin-settings-form.tsx
11
11
  import { useState as useState2, useEffect as useEffect2 } from "react";
12
12
  import {
13
13
  resolveLocalized as resolveLocalized3
14
14
  } from "ampless";
15
- import { Button as Button2, Input as Input2, Label as Label3, Textarea } from "@ampless/runtime/ui";
15
+ import { Button as Button2, Label as Label3 } from "@ampless/runtime/ui";
16
16
 
17
17
  // src/lib/plugin-settings.ts
18
18
  import {
@@ -38,6 +38,25 @@ async function setPluginPublicSetting(instanceId, field, rawValue) {
38
38
  const store = getKvStore();
39
39
  await store.put(SITE_CONFIG_PK, pluginSettingKey(instanceId, field.key), validated);
40
40
  }
41
+ async function getPluginPublicSetting(instanceId, fieldKey) {
42
+ return getKvStore().get(SITE_CONFIG_PK, pluginSettingKey(instanceId, fieldKey));
43
+ }
44
+ function collectSettingWrites(fields, values, touched, parseFn) {
45
+ const writes = [];
46
+ const invalid = {};
47
+ for (const field of fields) {
48
+ if (!touched[field.key]) continue;
49
+ const raw = values[field.key] ?? "";
50
+ const parsed = parseFn(field, raw);
51
+ if (parsed === null && raw !== "") {
52
+ invalid[field.key] = true;
53
+ continue;
54
+ }
55
+ if (parsed === null) continue;
56
+ writes.push({ field, parsed });
57
+ }
58
+ return { writes, invalid };
59
+ }
41
60
  async function deletePluginPublicSetting(instanceId, field) {
42
61
  if (!isValidPluginKey(instanceId)) {
43
62
  throw new Error(`Invalid plugin instanceId: "${instanceId}"`);
@@ -145,6 +164,114 @@ import { useEffect, useState } from "react";
145
164
  import { resolveLocalized } from "ampless";
146
165
  import { Label } from "@ampless/runtime/ui";
147
166
 
167
+ // src/components/scalar-input.tsx
168
+ import { Input, Textarea } from "@ampless/runtime/ui";
169
+ import { jsx, jsxs } from "react/jsx-runtime";
170
+ function renderScalarInput(field, id, value, invalid, onChange) {
171
+ switch (field.type) {
172
+ case "text":
173
+ case "url":
174
+ return /* @__PURE__ */ jsx(
175
+ Input,
176
+ {
177
+ id,
178
+ value,
179
+ maxLength: field.type === "text" ? field.maxLength : void 0,
180
+ placeholder: "placeholder" in field ? field.placeholder : void 0,
181
+ onChange: (e) => onChange(e.target.value),
182
+ "aria-invalid": invalid,
183
+ type: field.type === "url" ? "url" : "text"
184
+ }
185
+ );
186
+ case "textarea":
187
+ return /* @__PURE__ */ jsx(
188
+ Textarea,
189
+ {
190
+ id,
191
+ value,
192
+ rows: field.rows ?? 4,
193
+ maxLength: field.maxLength,
194
+ placeholder: field.placeholder,
195
+ onChange: (e) => onChange(e.target.value),
196
+ "aria-invalid": invalid
197
+ }
198
+ );
199
+ case "code":
200
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
201
+ field.language && /* @__PURE__ */ jsx("p", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: field.language }),
202
+ /* @__PURE__ */ jsx(
203
+ Textarea,
204
+ {
205
+ id,
206
+ value,
207
+ rows: field.rows ?? 8,
208
+ maxLength: field.maxLength,
209
+ placeholder: field.placeholder,
210
+ onChange: (e) => onChange(e.target.value),
211
+ "aria-invalid": invalid,
212
+ className: "font-mono text-xs"
213
+ }
214
+ )
215
+ ] });
216
+ case "boolean":
217
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
218
+ /* @__PURE__ */ jsx(
219
+ "input",
220
+ {
221
+ id,
222
+ type: "checkbox",
223
+ checked: value === "true",
224
+ onChange: (e) => onChange(e.target.checked ? "true" : "false"),
225
+ className: "h-4 w-4"
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: value === "true" ? "on" : "off" })
229
+ ] });
230
+ case "number":
231
+ return /* @__PURE__ */ jsx(
232
+ Input,
233
+ {
234
+ id,
235
+ type: "number",
236
+ value,
237
+ min: field.min,
238
+ max: field.max,
239
+ step: field.step,
240
+ onChange: (e) => onChange(e.target.value),
241
+ "aria-invalid": invalid
242
+ }
243
+ );
244
+ case "select":
245
+ return /* @__PURE__ */ jsxs(
246
+ "select",
247
+ {
248
+ id,
249
+ className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
250
+ value,
251
+ onChange: (e) => onChange(e.target.value),
252
+ "aria-invalid": invalid,
253
+ children: [
254
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
255
+ field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: typeof opt.label === "string" ? opt.label : opt.value }, opt.value))
256
+ ]
257
+ }
258
+ );
259
+ case "json":
260
+ return /* @__PURE__ */ jsx(
261
+ Textarea,
262
+ {
263
+ id,
264
+ value,
265
+ rows: field.rows ?? 8,
266
+ placeholder: field.placeholder ?? "{}",
267
+ onChange: (e) => onChange(e.target.value),
268
+ "aria-invalid": invalid,
269
+ className: "font-mono text-xs"
270
+ }
271
+ );
272
+ }
273
+ }
274
+
148
275
  // src/lib/repeatable-field.ts
149
276
  function parseRepeatableValue(raw) {
150
277
  if (!raw || raw.trim() === "") return [];
@@ -209,7 +336,7 @@ function canAddItem(field, currentCount) {
209
336
  }
210
337
 
211
338
  // src/components/repeatable-field-editor.tsx
212
- import { jsx, jsxs } from "react/jsx-runtime";
339
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
213
340
  function RepeatableFieldEditor({
214
341
  field,
215
342
  id,
@@ -245,7 +372,7 @@ function RepeatableFieldEditor({
245
372
  commit([...items, makeEmptyItem(field)]);
246
373
  }
247
374
  const addLabelText = field.addLabel ? resolveLocalized(field.addLabel, locale) : "+ Add item";
248
- return /* @__PURE__ */ jsxs(
375
+ return /* @__PURE__ */ jsxs2(
249
376
  "div",
250
377
  {
251
378
  id,
@@ -255,17 +382,17 @@ function RepeatableFieldEditor({
255
382
  invalid ? "border-destructive" : "border-border"
256
383
  ].join(" "),
257
384
  children: [
258
- items.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No items yet." }),
385
+ items.length === 0 && /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: "No items yet." }),
259
386
  items.map((item, itemIdx) => {
260
387
  const label = itemLabel(field, item, itemIdx);
261
- return /* @__PURE__ */ jsxs(
388
+ return /* @__PURE__ */ jsxs2(
262
389
  "div",
263
390
  {
264
391
  className: "space-y-2 rounded-md border border-border bg-muted/30 p-3",
265
392
  children: [
266
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
267
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: label }),
268
- /* @__PURE__ */ jsx(
393
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
394
+ /* @__PURE__ */ jsx2("span", { className: "text-xs font-medium", children: label }),
395
+ /* @__PURE__ */ jsx2(
269
396
  "button",
270
397
  {
271
398
  type: "button",
@@ -276,13 +403,13 @@ function RepeatableFieldEditor({
276
403
  }
277
404
  )
278
405
  ] }),
279
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: field.fields.map((sf) => {
406
+ /* @__PURE__ */ jsx2("div", { className: "space-y-2", children: field.fields.map((sf) => {
280
407
  const cellId = `${id}-${itemIdx}-${sf.key}`;
281
408
  const formString = subFieldValueToFormString(sf, item[sf.key]);
282
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
283
- /* @__PURE__ */ jsxs(Label, { htmlFor: cellId, className: "text-xs", children: [
409
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
410
+ /* @__PURE__ */ jsxs2(Label, { htmlFor: cellId, className: "text-xs", children: [
284
411
  resolveLocalized(sf.label, locale),
285
- sf.required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
412
+ sf.required && /* @__PURE__ */ jsx2("span", { className: "ml-1 text-destructive", children: "*" })
286
413
  ] }),
287
414
  renderScalarInput(
288
415
  sf,
@@ -299,7 +426,7 @@ function RepeatableFieldEditor({
299
426
  itemIdx
300
427
  );
301
428
  }),
302
- /* @__PURE__ */ jsx(
429
+ /* @__PURE__ */ jsx2(
303
430
  "button",
304
431
  {
305
432
  type: "button",
@@ -317,7 +444,7 @@ function RepeatableFieldEditor({
317
444
  // src/components/secret-field-input.tsx
318
445
  import { useReducer, useId } from "react";
319
446
  import { resolveLocalized as resolveLocalized2 } from "ampless";
320
- import { Button, Input, Label as Label2 } from "@ampless/runtime/ui";
447
+ import { Button, Input as Input2, Label as Label2 } from "@ampless/runtime/ui";
321
448
 
322
449
  // src/lib/secret-field-input.ts
323
450
  function initialSecretFieldState(hasValue) {
@@ -379,7 +506,7 @@ function isBusy(state) {
379
506
  }
380
507
 
381
508
  // src/components/secret-field-input.tsx
382
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
509
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
383
510
  function SecretFieldInput({ field, hasValue, onSave, onClear }) {
384
511
  const locale = useLocale();
385
512
  const inputId = useId();
@@ -416,15 +543,15 @@ function SecretFieldInput({ field, hasValue, onSave, onClear }) {
416
543
  const label = resolveLocalized2(field.label, locale);
417
544
  const description = field.description ? resolveLocalized2(field.description, locale) : void 0;
418
545
  const busy = isBusy(state);
419
- return /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5 rounded-md border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950/30", children: [
420
- /* @__PURE__ */ jsx2("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs2(Label2, { htmlFor: inputId, className: "flex items-center gap-1.5 text-sm font-medium", children: [
421
- /* @__PURE__ */ jsx2(LockIcon, {}),
546
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-1.5 rounded-md border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950/30", children: [
547
+ /* @__PURE__ */ jsx3("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs3(Label2, { htmlFor: inputId, className: "flex items-center gap-1.5 text-sm font-medium", children: [
548
+ /* @__PURE__ */ jsx3(LockIcon, {}),
422
549
  label,
423
- field.required && /* @__PURE__ */ jsx2("span", { className: "ml-0.5 text-destructive", children: "*" })
550
+ field.required && /* @__PURE__ */ jsx3("span", { className: "ml-0.5 text-destructive", children: "*" })
424
551
  ] }) }),
425
- description && /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: description }),
426
- showStoredPlaceholder(state) && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
427
- /* @__PURE__ */ jsx2(
552
+ description && /* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: description }),
553
+ showStoredPlaceholder(state) && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
554
+ /* @__PURE__ */ jsx3(
428
555
  "span",
429
556
  {
430
557
  className: "flex-1 rounded-md border bg-background px-3 py-1.5 text-sm text-muted-foreground",
@@ -432,7 +559,7 @@ function SecretFieldInput({ field, hasValue, onSave, onClear }) {
432
559
  children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
433
560
  }
434
561
  ),
435
- /* @__PURE__ */ jsx2(
562
+ /* @__PURE__ */ jsx3(
436
563
  Button,
437
564
  {
438
565
  type: "button",
@@ -443,7 +570,7 @@ function SecretFieldInput({ field, hasValue, onSave, onClear }) {
443
570
  children: "Replace"
444
571
  }
445
572
  ),
446
- /* @__PURE__ */ jsx2(
573
+ /* @__PURE__ */ jsx3(
447
574
  Button,
448
575
  {
449
576
  type: "button",
@@ -456,9 +583,9 @@ function SecretFieldInput({ field, hasValue, onSave, onClear }) {
456
583
  }
457
584
  )
458
585
  ] }),
459
- showInput(state) && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
460
- /* @__PURE__ */ jsx2(
461
- Input,
586
+ showInput(state) && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
587
+ /* @__PURE__ */ jsx3(
588
+ Input2,
462
589
  {
463
590
  id: inputId,
464
591
  type: "text",
@@ -474,7 +601,7 @@ function SecretFieldInput({ field, hasValue, onSave, onClear }) {
474
601
  onChange: (e) => dispatch({ type: "CHANGE", value: e.target.value })
475
602
  }
476
603
  ),
477
- /* @__PURE__ */ jsx2(
604
+ /* @__PURE__ */ jsx3(
478
605
  Button,
479
606
  {
480
607
  type: "button",
@@ -484,7 +611,7 @@ function SecretFieldInput({ field, hasValue, onSave, onClear }) {
484
611
  children: state.status === "saving" ? "Saving\u2026" : "Save"
485
612
  }
486
613
  ),
487
- (state.status === "editing" || state.status === "error" && state.previousStatus === "editing") && /* @__PURE__ */ jsx2(
614
+ (state.status === "editing" || state.status === "error" && state.previousStatus === "editing") && /* @__PURE__ */ jsx3(
488
615
  Button,
489
616
  {
490
617
  type: "button",
@@ -496,12 +623,12 @@ function SecretFieldInput({ field, hasValue, onSave, onClear }) {
496
623
  }
497
624
  )
498
625
  ] }),
499
- state.status === "clearing" && /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: "Clearing\u2026" }),
500
- state.status === "error" && /* @__PURE__ */ jsx2("p", { className: "text-xs text-destructive", children: state.message })
626
+ state.status === "clearing" && /* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: "Clearing\u2026" }),
627
+ state.status === "error" && /* @__PURE__ */ jsx3("p", { className: "text-xs text-destructive", children: state.message })
501
628
  ] });
502
629
  }
503
630
  function LockIcon() {
504
- return /* @__PURE__ */ jsx2(
631
+ return /* @__PURE__ */ jsx3(
505
632
  "svg",
506
633
  {
507
634
  xmlns: "http://www.w3.org/2000/svg",
@@ -509,7 +636,7 @@ function LockIcon() {
509
636
  fill: "currentColor",
510
637
  className: "h-3.5 w-3.5 text-amber-600 dark:text-amber-400",
511
638
  "aria-hidden": "true",
512
- children: /* @__PURE__ */ jsx2(
639
+ children: /* @__PURE__ */ jsx3(
513
640
  "path",
514
641
  {
515
642
  fillRule: "evenodd",
@@ -522,7 +649,7 @@ function LockIcon() {
522
649
  }
523
650
 
524
651
  // src/components/plugin-settings-form.tsx
525
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
652
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
526
653
  var CACHE_REBUILD_DELAY_MS = 8e3;
527
654
  function stringify(field, raw) {
528
655
  if (raw === void 0 || raw === null) return "";
@@ -614,6 +741,56 @@ function PluginSettingsForm({
614
741
  cancelled = true;
615
742
  };
616
743
  }, [instanceId]);
744
+ useEffect2(() => {
745
+ if (fields.length === 0) return;
746
+ let cancelled = false;
747
+ async function fetchStoredValues() {
748
+ const updates = {};
749
+ const newStoredKeys = [];
750
+ const clearedKeys = [];
751
+ for (const field of fields) {
752
+ try {
753
+ const stored = await getPluginPublicSetting(instanceId, field.key);
754
+ if (stored !== null && stored !== void 0) {
755
+ updates[field.key] = stringify(field, stored);
756
+ newStoredKeys.push(field.key);
757
+ } else {
758
+ clearedKeys.push(field.key);
759
+ }
760
+ } catch (err) {
761
+ console.warn("[plugin] mount fetch failed for field", field.key, err);
762
+ }
763
+ }
764
+ if (cancelled) return;
765
+ if (Object.keys(updates).length > 0 || clearedKeys.length > 0) {
766
+ setState((prev) => {
767
+ const nextValues = { ...prev.values };
768
+ for (const [key, val] of Object.entries(updates)) {
769
+ if (!prev.touched[key]) {
770
+ nextValues[key] = val;
771
+ }
772
+ }
773
+ for (const key of clearedKeys) {
774
+ if (!prev.touched[key]) {
775
+ const field = fields.find((f) => f.key === key);
776
+ if (field) nextValues[key] = defaultDisplay(field);
777
+ }
778
+ }
779
+ return { ...prev, values: nextValues };
780
+ });
781
+ setStoredKeys((prev) => {
782
+ const next = new Set(prev);
783
+ for (const k of newStoredKeys) next.add(k);
784
+ for (const k of clearedKeys) next.delete(k);
785
+ return next;
786
+ });
787
+ }
788
+ }
789
+ void fetchStoredValues();
790
+ return () => {
791
+ cancelled = true;
792
+ };
793
+ }, [instanceId]);
617
794
  function defaultDisplay(field) {
618
795
  return field.default !== void 0 ? stringify(field, field.default) : "";
619
796
  }
@@ -676,38 +853,41 @@ function PluginSettingsForm({
676
853
  setSaving(true);
677
854
  setError(null);
678
855
  setInfo(null);
679
- const newInvalid = {};
680
- const writes = [];
681
- const writtenKeys = [];
682
- for (const field of fields) {
683
- if (!state.touched[field.key]) continue;
684
- const raw = state.values[field.key] ?? "";
685
- const parsed = parse(field, raw);
686
- if (parsed === null && raw !== "") {
687
- newInvalid[field.key] = true;
688
- continue;
689
- }
690
- if (parsed === null) continue;
691
- writes.push(setPluginPublicSetting(instanceId, field, parsed));
692
- writtenKeys.push(field.key);
693
- }
856
+ const { writes: pendingWrites, invalid: newInvalid } = collectSettingWrites(
857
+ fields,
858
+ state.values,
859
+ state.touched,
860
+ parse
861
+ );
694
862
  if (Object.keys(newInvalid).length > 0) {
695
863
  setState((prev) => ({ ...prev, invalid: newInvalid }));
696
864
  setSaving(false);
697
865
  setError(t("plugins.invalidValue"));
698
866
  return;
699
867
  }
868
+ if (pendingWrites.length === 0) {
869
+ setInfo(t("plugins.noChanges"));
870
+ setSaving(false);
871
+ return;
872
+ }
700
873
  try {
701
- await Promise.all(writes);
874
+ await Promise.all(
875
+ pendingWrites.map(
876
+ ({ field, parsed }) => setPluginPublicSetting(instanceId, field, parsed)
877
+ )
878
+ );
879
+ const writtenKeys = pendingWrites.map(({ field }) => field.key);
702
880
  setInfo(t("plugins.saved"));
703
- setState((prev) => ({ ...prev, touched: {}, invalid: {} }));
704
- if (writtenKeys.length > 0) {
705
- setStoredKeys((prev) => {
706
- const next = new Set(prev);
707
- for (const k of writtenKeys) next.add(k);
708
- return next;
709
- });
710
- }
881
+ setState((prev) => {
882
+ const nextTouched = { ...prev.touched };
883
+ for (const k of writtenKeys) delete nextTouched[k];
884
+ return { ...prev, touched: nextTouched, invalid: {} };
885
+ });
886
+ setStoredKeys((prev) => {
887
+ const next = new Set(prev);
888
+ for (const k of writtenKeys) next.add(k);
889
+ return next;
890
+ });
711
891
  scheduleCacheInvalidation();
712
892
  } catch (err) {
713
893
  console.error("[plugin] save failed", err);
@@ -716,13 +896,13 @@ function PluginSettingsForm({
716
896
  setSaving(false);
717
897
  }
718
898
  }
719
- return /* @__PURE__ */ jsxs3("form", { onSubmit: save, className: "max-w-2xl space-y-5 rounded-md border p-4", children: [
720
- /* @__PURE__ */ jsxs3("div", { children: [
721
- /* @__PURE__ */ jsx3("h2", { className: "text-base font-semibold", children: displayName ? resolveLocalized3(displayName, locale) : instanceId }),
722
- /* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: instanceId })
899
+ return /* @__PURE__ */ jsxs4("form", { onSubmit: save, className: "max-w-2xl space-y-5 rounded-md border p-4", children: [
900
+ /* @__PURE__ */ jsxs4("div", { children: [
901
+ /* @__PURE__ */ jsx4("h2", { className: "text-base font-semibold", children: displayName ? resolveLocalized3(displayName, locale) : instanceId }),
902
+ /* @__PURE__ */ jsx4("p", { className: "text-xs text-muted-foreground", children: instanceId })
723
903
  ] }),
724
- fields.length > 0 && /* @__PURE__ */ jsxs3(Fragment, { children: [
725
- fields.map((field) => /* @__PURE__ */ jsx3(
904
+ fields.length > 0 && /* @__PURE__ */ jsxs4(Fragment, { children: [
905
+ fields.map((field) => /* @__PURE__ */ jsx4(
726
906
  FieldRow,
727
907
  {
728
908
  field,
@@ -734,14 +914,14 @@ function PluginSettingsForm({
734
914
  },
735
915
  field.key
736
916
  )),
737
- info && /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: info }),
738
- error && /* @__PURE__ */ jsx3("p", { className: "text-sm text-destructive", children: error }),
739
- /* @__PURE__ */ jsx3(Button2, { type: "submit", disabled: saving, children: saving ? t("plugins.saving") : t("plugins.save") })
917
+ info && /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", children: info }),
918
+ error && /* @__PURE__ */ jsx4("p", { className: "text-sm text-destructive", children: error }),
919
+ /* @__PURE__ */ jsx4(Button2, { type: "submit", disabled: saving, children: saving ? t("plugins.saving") : t("plugins.save") })
740
920
  ] }),
741
- secretFields && secretFields.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "mt-6 space-y-4 border-t pt-4", children: [
742
- /* @__PURE__ */ jsxs3("div", { children: [
743
- /* @__PURE__ */ jsxs3("h3", { className: "flex items-center gap-1.5 text-sm font-semibold text-amber-700 dark:text-amber-400", children: [
744
- /* @__PURE__ */ jsx3(
921
+ secretFields && secretFields.length > 0 && /* @__PURE__ */ jsxs4("div", { className: "mt-6 space-y-4 border-t pt-4", children: [
922
+ /* @__PURE__ */ jsxs4("div", { children: [
923
+ /* @__PURE__ */ jsxs4("h3", { className: "flex items-center gap-1.5 text-sm font-semibold text-amber-700 dark:text-amber-400", children: [
924
+ /* @__PURE__ */ jsx4(
745
925
  "svg",
746
926
  {
747
927
  xmlns: "http://www.w3.org/2000/svg",
@@ -749,7 +929,7 @@ function PluginSettingsForm({
749
929
  fill: "currentColor",
750
930
  className: "h-3.5 w-3.5",
751
931
  "aria-hidden": "true",
752
- children: /* @__PURE__ */ jsx3(
932
+ children: /* @__PURE__ */ jsx4(
753
933
  "path",
754
934
  {
755
935
  fillRule: "evenodd",
@@ -761,12 +941,12 @@ function PluginSettingsForm({
761
941
  ),
762
942
  "Secret settings"
763
943
  ] }),
764
- /* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: "Values are stored securely and never displayed after saving. They are only accessible by the trusted processor Lambda \u2014 not by the public site or admin UI." })
944
+ /* @__PURE__ */ jsx4("p", { className: "text-xs text-muted-foreground", children: "Values are stored securely and never displayed after saving. They are only accessible by the trusted processor Lambda \u2014 not by the public site or admin UI." })
765
945
  ] }),
766
946
  secretHasValue === null ? (
767
947
  // Loading placeholder. Keep layout height stable so the
768
948
  // section doesn't jump when the check resolves.
769
- /* @__PURE__ */ jsx3(
949
+ /* @__PURE__ */ jsx4(
770
950
  "div",
771
951
  {
772
952
  className: "h-10 animate-pulse rounded-md bg-muted/50",
@@ -774,7 +954,7 @@ function PluginSettingsForm({
774
954
  "aria-label": t("plugins.loading")
775
955
  }
776
956
  )
777
- ) : secretFields.map((field) => /* @__PURE__ */ jsx3(
957
+ ) : secretFields.map((field) => /* @__PURE__ */ jsx4(
778
958
  SecretFieldInput,
779
959
  {
780
960
  field,
@@ -791,17 +971,17 @@ function FieldRow({ field, value, invalid, onChange, onReset, hasStoredValue })
791
971
  const t = useT();
792
972
  const locale = useLocale();
793
973
  const id = `plugin-${field.key}`;
794
- const labelEl = /* @__PURE__ */ jsxs3(Label3, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: [
974
+ const labelEl = /* @__PURE__ */ jsxs4(Label3, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: [
795
975
  resolveLocalized3(field.label, locale),
796
- field.required && /* @__PURE__ */ jsx3("span", { className: "ml-1 text-destructive", children: "*" })
976
+ field.required && /* @__PURE__ */ jsx4("span", { className: "ml-1 text-destructive", children: "*" })
797
977
  ] });
798
- const description = field.description ? /* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: resolveLocalized3(field.description, locale) }) : null;
978
+ const description = field.description ? /* @__PURE__ */ jsx4("p", { className: "text-xs text-muted-foreground", children: resolveLocalized3(field.description, locale) }) : null;
799
979
  const input = renderInput(field, id, value, invalid, onChange);
800
980
  const placeholder = renderDefaultHint(field, locale);
801
- return /* @__PURE__ */ jsxs3("div", { className: "space-y-1.5", children: [
802
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
981
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
982
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
803
983
  labelEl,
804
- hasStoredValue && /* @__PURE__ */ jsx3(
984
+ hasStoredValue && /* @__PURE__ */ jsx4(
805
985
  "button",
806
986
  {
807
987
  type: "button",
@@ -830,116 +1010,12 @@ function renderDefaultHint(field, _locale) {
830
1010
  }
831
1011
  }
832
1012
  if (!preview) return null;
833
- return /* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: /* @__PURE__ */ jsx3("code", { children: preview }) });
834
- }
835
- function renderScalarInput(field, id, value, invalid, onChange) {
836
- switch (field.type) {
837
- case "text":
838
- case "url":
839
- return /* @__PURE__ */ jsx3(
840
- Input2,
841
- {
842
- id,
843
- value,
844
- maxLength: field.type === "text" ? field.maxLength : void 0,
845
- placeholder: "placeholder" in field ? field.placeholder : void 0,
846
- onChange: (e) => onChange(e.target.value),
847
- "aria-invalid": invalid,
848
- type: field.type === "url" ? "url" : "text"
849
- }
850
- );
851
- case "textarea":
852
- return /* @__PURE__ */ jsx3(
853
- Textarea,
854
- {
855
- id,
856
- value,
857
- rows: field.rows ?? 4,
858
- maxLength: field.maxLength,
859
- placeholder: field.placeholder,
860
- onChange: (e) => onChange(e.target.value),
861
- "aria-invalid": invalid
862
- }
863
- );
864
- case "code":
865
- return /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
866
- field.language && /* @__PURE__ */ jsx3("p", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: field.language }),
867
- /* @__PURE__ */ jsx3(
868
- Textarea,
869
- {
870
- id,
871
- value,
872
- rows: field.rows ?? 8,
873
- maxLength: field.maxLength,
874
- placeholder: field.placeholder,
875
- onChange: (e) => onChange(e.target.value),
876
- "aria-invalid": invalid,
877
- className: "font-mono text-xs"
878
- }
879
- )
880
- ] });
881
- case "boolean":
882
- return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
883
- /* @__PURE__ */ jsx3(
884
- "input",
885
- {
886
- id,
887
- type: "checkbox",
888
- checked: value === "true",
889
- onChange: (e) => onChange(e.target.checked ? "true" : "false"),
890
- className: "h-4 w-4"
891
- }
892
- ),
893
- /* @__PURE__ */ jsx3("span", { className: "text-xs text-muted-foreground", children: value === "true" ? "on" : "off" })
894
- ] });
895
- case "number":
896
- return /* @__PURE__ */ jsx3(
897
- Input2,
898
- {
899
- id,
900
- type: "number",
901
- value,
902
- min: field.min,
903
- max: field.max,
904
- step: field.step,
905
- onChange: (e) => onChange(e.target.value),
906
- "aria-invalid": invalid
907
- }
908
- );
909
- case "select":
910
- return /* @__PURE__ */ jsxs3(
911
- "select",
912
- {
913
- id,
914
- className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
915
- value,
916
- onChange: (e) => onChange(e.target.value),
917
- "aria-invalid": invalid,
918
- children: [
919
- /* @__PURE__ */ jsx3("option", { value: "", children: "\u2014" }),
920
- field.options.map((opt) => /* @__PURE__ */ jsx3("option", { value: opt.value, children: typeof opt.label === "string" ? opt.label : opt.value }, opt.value))
921
- ]
922
- }
923
- );
924
- case "json":
925
- return /* @__PURE__ */ jsx3(
926
- Textarea,
927
- {
928
- id,
929
- value,
930
- rows: field.rows ?? 8,
931
- placeholder: field.placeholder ?? "{}",
932
- onChange: (e) => onChange(e.target.value),
933
- "aria-invalid": invalid,
934
- className: "font-mono text-xs"
935
- }
936
- );
937
- }
1013
+ return /* @__PURE__ */ jsx4("p", { className: "text-xs text-muted-foreground", children: /* @__PURE__ */ jsx4("code", { children: preview }) });
938
1014
  }
939
1015
  function renderInput(field, id, value, invalid, onChange) {
940
1016
  switch (field.type) {
941
1017
  case "repeatable":
942
- return /* @__PURE__ */ jsx3(
1018
+ return /* @__PURE__ */ jsx4(
943
1019
  RepeatableFieldEditor,
944
1020
  {
945
1021
  field,
@@ -955,6 +1031,5 @@ function renderInput(field, id, value, invalid, onChange) {
955
1031
  }
956
1032
 
957
1033
  export {
958
- PluginSettingsForm,
959
- renderScalarInput
1034
+ PluginSettingsForm
960
1035
  };