@cosmicdrift/kumiko-renderer 0.39.0 → 0.40.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer",
3
- "version": "0.39.0",
3
+ "version": "0.40.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>",
@@ -5,13 +5,13 @@ import type {
5
5
  EntityDefinition,
6
6
  EntityEditScreenDefinition,
7
7
  EntityListScreenDefinition,
8
- FieldCondition,
9
8
  RowAction,
10
9
  RowActionWriteHandler,
11
10
  RowFieldExtractor,
12
11
  ScreenDefinition,
13
12
  ToolbarAction,
14
13
  } from "@cosmicdrift/kumiko-framework/ui-types";
14
+ import { evalFieldCondition } from "@cosmicdrift/kumiko-framework/ui-types";
15
15
  import type {
16
16
  Command,
17
17
  FormSnapshot,
@@ -46,13 +46,6 @@ function evalRowExtractor(
46
46
  return Object.fromEntries(Object.entries(extractor.map).map(([to, from]) => [to, row[from]]));
47
47
  }
48
48
 
49
- function evalFieldCondition(cond: FieldCondition, values: Record<string, unknown>): boolean {
50
- if (typeof cond === "boolean") return cond;
51
- const val = values[cond.field];
52
- if ("eq" in cond) return val === cond.eq;
53
- return val !== cond.ne;
54
- }
55
-
56
49
  // KumikoScreen picks up a ScreenDefinition from the schema by qn and
57
50
  // routes it to the right renderer based on `screen.type`. Command
58
51
  // qualification (`<feature>:write:<entity>:create` etc.) happens here
@@ -695,9 +688,16 @@ function EntityListBody({
695
688
  // immer da (Provider von createKumikoApp).
696
689
  if (action.kind === "navigate") {
697
690
  // Default entityId für entityEdit-Targets: row["id"] wenn
698
- // kein expliziter entityId-Feldname gesetzt ist.
691
+ // kein expliziter entityId-Feldname gesetzt ist. Nur für Targets
692
+ // DERSELBEN Entity — ein Cross-Entity-Edit-Screen bekäme sonst die
693
+ // falsche row.id injiziert, und "Duplicate → Create"-Patterns
694
+ // würden in den Update-Mode gezwungen. Cross-Entity-Navigation
695
+ // setzt action.entityId explizit.
699
696
  const targetIsEntityEdit = schema.screens.some(
700
- (s) => s.type === "entityEdit" && lastSegment(s.id) === action.screen,
697
+ (s) =>
698
+ s.type === "entityEdit" &&
699
+ s.entity === screen.entity &&
700
+ lastSegment(s.id) === action.screen,
701
701
  );
702
702
  const actionVisible = action.visible;
703
703
  return {
@@ -731,6 +731,11 @@ function EntityListBody({
731
731
  // NACH navigate: pushState trägt keine Query — Params die
732
732
  // vor dem Push gesetzt werden, kleben an der ALTEN URL und
733
733
  // sind auf dem Ziel-Screen weg (actionForm-Prefill leer).
734
+ // Bekannte Kante (bewusst offen): zielt die Action auf den
735
+ // AKTUELLEN pathname, short-circuit't pushPath ohne die Query
736
+ // zu leeren — die neuen Params mergen dann auf den alten
737
+ // ?-String. Für Row-Actions praktisch nicht erreichbar
738
+ // (Pfad differiert über entityId/screen).
734
739
  nav.setSearchParams(stringified);
735
740
  }
736
741
  },
@@ -777,7 +782,7 @@ function EntityListBody({
777
782
  };
778
783
  })
779
784
  .filter((a: DataTableRowAction | null): a is DataTableRowAction => a !== null);
780
- }, [screen.rowActions, effectiveTranslate, dispatcher, nav, schema.screens]);
785
+ }, [screen.rowActions, screen.entity, effectiveTranslate, dispatcher, nav, schema.screens]);
781
786
 
782
787
  // ToolbarActions: Schema → Resolved-Form (analog rowActions).
783
788
  // navigate-kind → useNav().navigate({ screenId }), writeHandler-kind
@@ -1147,7 +1152,9 @@ function ConfigEditBody({
1147
1152
  {...(translate !== undefined && { translate })}
1148
1153
  labelAppendix={(fieldName: string) => {
1149
1154
  const source = sources[fieldName];
1150
- return source ? <ConfigSourceBadge source={source} /> : undefined;
1155
+ return source ? (
1156
+ <ConfigSourceBadge source={source} screenScope={screen.scope} />
1157
+ ) : undefined;
1151
1158
  }}
1152
1159
  fieldAppendix={(fieldName: string) => {
1153
1160
  const cascade = cascades[fieldName];
@@ -3,7 +3,11 @@ import type {
3
3
  EntityEditScreenDefinition,
4
4
  FieldCondition,
5
5
  } from "@cosmicdrift/kumiko-framework/ui-types";
6
- import { isExtensionEditSection, normalizeEditField } from "@cosmicdrift/kumiko-framework/ui-types";
6
+ import {
7
+ evalFieldCondition,
8
+ isExtensionEditSection,
9
+ normalizeEditField,
10
+ } from "@cosmicdrift/kumiko-framework/ui-types";
7
11
  import type {
8
12
  DispatcherError,
9
13
  EditExtensionSectionViewModel,
@@ -84,12 +88,8 @@ function toConditionValue<TValues extends FormValues, TCtx>(
84
88
  cond: FieldCondition,
85
89
  ): NonNullable<FieldConditions<TValues, TCtx>["visible"]> {
86
90
  if (typeof cond === "boolean") return cond;
87
- if ("eq" in cond) {
88
- const { field, eq } = cond;
89
- return (values: TValues) => (values as Record<string, unknown>)[field] === eq;
90
- }
91
- const { field, ne } = cond;
92
- return (values: TValues) => (values as Record<string, unknown>)[field] !== ne;
91
+ // @cast-boundary form-values: TValues ist strukturell ein Record.
92
+ return (values: TValues) => evalFieldCondition(cond, values as Record<string, unknown>);
93
93
  }
94
94
 
95
95
  function deriveFormFields<TValues extends FormValues, TCtx>(
@@ -39,8 +39,9 @@ export function RenderField({
39
39
  const { Field, Input } = usePrimitives();
40
40
  // App-Locale (i18n) für money/date-Inputs — sonst fielen sie auf
41
41
  // navigator.language (Browser-Sprache) zurück statt der gewählten
42
- // App-Sprache. useLocale() wirft ohne LocaleProvider; ok, weil
43
- // RenderField nur unter RenderEdit im Kumiko-App-Tree läuft.
42
+ // App-Sprache. BEWUSSTE API-Verschärfung (seit 0.38): RenderField ist
43
+ // public exportiert und verlangt jetzt einen LocaleProvider —
44
+ // Standalone-Consumer/Tests müssen wrappen (createKumikoApp tut es).
44
45
  const appLocale = useLocale().locale();
45
46
  if (!field.visible) return null;
46
47
 
@@ -486,9 +486,16 @@ export type DialogProps = {
486
486
  };
487
487
 
488
488
  /** Source-badge for one cascade step (User / Tenant / System / …).
489
- * Used inline next to a config value to indicate where it came from. */
489
+ * Used inline next to a config value to indicate where it came from.
490
+ * Requires a LocaleProvider above it (labels run through useTranslation)
491
+ * — createKumikoApp wires one; standalone consumers must wrap. */
490
492
  export type ConfigSourceBadgeProps = {
491
493
  readonly source: ConfigValueSource;
494
+ /** Scope of the hosting screen. Non-system screens collapse sources
495
+ * ABOVE their scope (system-row/app-override/computed) into the neutral
496
+ * default badge — operator internals stay invisible to tenants, same
497
+ * rule as ConfigCascadeView's toDisplayLevels. */
498
+ readonly screenScope?: ConfigScope;
492
499
  };
493
500
 
494
501
  /** Collapsible cascade-view that lives under a config-edit input.