@cosmicdrift/kumiko-renderer 0.368.0 → 0.369.0

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.
@@ -2655,6 +2655,13 @@ function buildConfigEditDefaults(fields) {
2655
2655
  }
2656
2656
  return defaults;
2657
2657
  }
2658
+ // A field hidden by its `visible` condition (a provider's settings while another
2659
+ // provider is picked) is not saved: the form saves what it shows.
2660
+ function hiddenLayoutFieldNames(screen, values) {
2661
+ return new Set(layoutEditFields(screen)
2662
+ .filter((spec) => spec.visible !== undefined && !evalFieldCondition(spec.visible, values))
2663
+ .map((spec) => spec.field));
2664
+ }
2658
2665
  function isEmptyNumericFormValue(value) {
2659
2666
  if (value === undefined || value === null || value === "")
2660
2667
  return true;
@@ -2670,6 +2677,8 @@ function ConfigEditBody({ schema, screen, translate, }) {
2670
2677
  // unsere Form-Field-Werte.
2671
2678
  const valuesQuery = useQuery("config:query:values", {});
2672
2679
  const cascadeQuery = useQuery("config:query:cascade", {});
2680
+ const secretKeys = screen.secretKeys;
2681
+ const secretsQuery = useQuery("secrets:query:list", {}, { enabled: secretKeys !== undefined });
2673
2682
  const synthEntity = useMemo(() => synthesizeConfigEditEntity(screen.fields), [screen.fields]);
2674
2683
  const synthScreen = useMemo(() => synthesizeConfigEditScreen(screen), [screen]);
2675
2684
  const boundsSchema = useMemo(() => buildConfigEditSchema(screen.fields), [screen.fields]);
@@ -2681,9 +2690,17 @@ function ConfigEditBody({ schema, screen, translate, }) {
2681
2690
  const initial = useMemo(() => {
2682
2691
  if (valuesQuery.data === null)
2683
2692
  return null;
2693
+ if (secretKeys !== undefined && secretsQuery.data === null)
2694
+ return null;
2695
+ const storedSecretKeys = new Set((secretsQuery.data ?? []).map((row) => row.key));
2684
2696
  const out = {};
2685
2697
  const defaults = buildConfigEditDefaults(screen.fields);
2686
2698
  for (const [shortName, fieldDef] of Object.entries(screen.fields)) {
2699
+ const secretKey = secretKeys?.[shortName];
2700
+ if (secretKey !== undefined) {
2701
+ out[shortName] = storedSecretKeys.has(secretKey) ? true : null;
2702
+ continue;
2703
+ }
2687
2704
  const qualified = screen.configKeys[shortName];
2688
2705
  if (qualified === undefined) {
2689
2706
  // Author hat ein Field deklariert ohne Mapping — nimm Default.
@@ -2719,7 +2736,14 @@ function ConfigEditBody({ schema, screen, translate, }) {
2719
2736
  }
2720
2737
  }
2721
2738
  return out;
2722
- }, [valuesQuery.data, screen.fields, screen.configKeys, screen.scope]);
2739
+ }, [
2740
+ valuesQuery.data,
2741
+ secretsQuery.data,
2742
+ secretKeys,
2743
+ screen.fields,
2744
+ screen.configKeys,
2745
+ screen.scope,
2746
+ ]);
2723
2747
  // Cascade-Lookup: qualifiedKey → ConfigCascade für die
2724
2748
  // Cascade-Anzeige unter jedem Feld. Defensive `levels`-Shape-Check
2725
2749
  // damit fremde Query-Mocks (z.B. configEdit-Unit-Tests die nur
@@ -2749,7 +2773,23 @@ function ConfigEditBody({ schema, screen, translate, }) {
2749
2773
  const customSubmit = useCallback(async (snapshot) => {
2750
2774
  const commands = [];
2751
2775
  const commandFieldNames = [];
2776
+ const hiddenFieldNames = hiddenLayoutFieldNames(synthScreen, snapshot.values);
2752
2777
  for (const [shortName, value] of Object.entries(snapshot.changes)) {
2778
+ if (hiddenFieldNames.has(shortName))
2779
+ continue;
2780
+ const secretKey = secretKeys?.[shortName];
2781
+ if (secretKey !== undefined) {
2782
+ // Same wire shape as a writeOnly config field: null removes, a string replaces.
2783
+ if (value === null) {
2784
+ commandFieldNames.push(shortName);
2785
+ commands.push({ type: "secrets:write:delete", payload: { key: secretKey } });
2786
+ }
2787
+ else if (typeof value === "string" && value.trim() !== "") {
2788
+ commandFieldNames.push(shortName);
2789
+ commands.push({ type: "secrets:write:set", payload: { key: secretKey, value } });
2790
+ }
2791
+ continue;
2792
+ }
2753
2793
  const qualified = screen.configKeys[shortName];
2754
2794
  if (qualified === undefined)
2755
2795
  continue;
@@ -2821,20 +2861,28 @@ function ConfigEditBody({ schema, screen, translate, }) {
2821
2861
  // bleibt die Anzeige stale bis Reload — gleiche refetch-Calls wie onReset.
2822
2862
  // allSettled: der Write ist schon committed, ein gescheitertes Refetch
2823
2863
  // (stale Anzeige) darf das Erfolgsergebnis nicht in einen Fehler kippen.
2824
- await Promise.allSettled([valuesQuery.refetch?.(), cascadeQuery.refetch?.()]);
2864
+ await Promise.allSettled([
2865
+ valuesQuery.refetch?.(),
2866
+ cascadeQuery.refetch?.(),
2867
+ ...(secretKeys !== undefined ? [secretsQuery.refetch?.()] : []),
2868
+ ]);
2825
2869
  return { validationBlocked: false, isSuccess: true, data: undefined };
2826
2870
  }, [
2827
2871
  dispatcher,
2872
+ synthScreen,
2873
+ secretKeys,
2828
2874
  screen.configKeys,
2829
2875
  screen.fields,
2830
2876
  screen.scope,
2831
2877
  valuesQuery.refetch,
2832
2878
  cascadeQuery.refetch,
2879
+ secretsQuery.refetch,
2833
2880
  ]);
2834
2881
  if (valuesQuery.loading && valuesQuery.data === null)
2835
2882
  return _jsx(QueryLoadingBanner, {});
2836
- if (valuesQuery.error) {
2837
- return (_jsx(QueryErrorBanner, { error: valuesQuery.error, translate: effectiveTranslate, onRetry: valuesQuery.refetch }));
2883
+ const failedQuery = valuesQuery.error ? valuesQuery : secretsQuery.error ? secretsQuery : null;
2884
+ if (failedQuery?.error) {
2885
+ return (_jsx(QueryErrorBanner, { error: failedQuery.error, translate: effectiveTranslate, onRetry: failedQuery.refetch }));
2838
2886
  }
2839
2887
  if (initial === null)
2840
2888
  return _jsx(QueryLoadingBanner, {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer",
3
- "version": "0.368.0",
3
+ "version": "0.369.0",
4
4
  "description": "Platform-agnostic React renderer for Kumiko screens. Contains the shared logic — primitives-contract, hooks, KumikoScreen, navigation & SSE abstractions — that any platform-specific renderer (web, native) composes. No DOM, no EventSource, no react-dom.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -15,9 +15,9 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@cosmicdrift/kumiko-framework": "0.368.0",
19
- "@cosmicdrift/kumiko-headless": "0.368.0",
20
- "@cosmicdrift/kumiko-types": "0.368.0",
18
+ "@cosmicdrift/kumiko-framework": "0.369.0",
19
+ "@cosmicdrift/kumiko-headless": "0.369.0",
20
+ "@cosmicdrift/kumiko-types": "0.369.0",
21
21
  "react": "^19.2.6",
22
22
  "temporal-polyfill": "^0.3.2",
23
23
  "zod": "^4.4.3"
@@ -28,7 +28,7 @@
28
28
  "@types/react-dom": "^19.2.3",
29
29
  "jsdom": "^29.1.1",
30
30
  "react-dom": "^19.2.6",
31
- "@cosmicdrift/kumiko-locale-de": "0.368.0"
31
+ "@cosmicdrift/kumiko-locale-de": "0.369.0"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",