@cosmicdrift/kumiko-renderer 0.242.0 → 0.243.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.242.0",
3
+ "version": "0.243.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,8 +15,8 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@cosmicdrift/kumiko-framework": "0.242.0",
19
- "@cosmicdrift/kumiko-headless": "0.242.0",
18
+ "@cosmicdrift/kumiko-framework": "0.243.0",
19
+ "@cosmicdrift/kumiko-headless": "0.243.0",
20
20
  "react": "^19.2.6",
21
21
  "temporal-polyfill": "^0.3.2",
22
22
  "zod": "^4.4.3"
@@ -27,7 +27,7 @@
27
27
  "@types/react-dom": "^19.2.3",
28
28
  "jsdom": "^29.1.1",
29
29
  "react-dom": "^19.2.6",
30
- "@cosmicdrift/kumiko-locale-de": "0.242.0"
30
+ "@cosmicdrift/kumiko-locale-de": "0.243.0"
31
31
  },
32
32
  "repository": {
33
33
  "type": "git",
@@ -25,7 +25,7 @@ import type {
25
25
  } from "@cosmicdrift/kumiko-headless";
26
26
  import { fieldLabelKey, fieldOptionLabelKey } from "@cosmicdrift/kumiko-headless";
27
27
  import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
28
- import { extractCreatedId } from "../components/reference-create-dialog";
28
+ import { extractCreatedId, extractIdField } from "../components/reference-create-dialog";
29
29
  import { RenderEdit, type RenderEditAction } from "../components/render-edit";
30
30
  import { RenderList, type ToolbarActionButton } from "../components/render-list";
31
31
  import { useDispatcher, useOptionalDispatcher } from "../context/dispatcher-context";
@@ -2425,6 +2425,12 @@ function ProjectionDetailBody({
2425
2425
  }
2426
2426
  // ---- actionForm (Tier 2.7d) ----
2427
2427
 
2428
+ function redirectScreenTarget(
2429
+ redirect: NonNullable<ActionFormScreenDefinition["redirect"]>,
2430
+ ): string {
2431
+ return typeof redirect === "string" ? redirect : redirect.screen;
2432
+ }
2433
+
2428
2434
  // Action-Form-Body — non-CRUD Write-Handler-driven Form. Re-uses
2429
2435
  // RenderEdit über synthetisierte EntityDefinition + EntityEditScreen-
2430
2436
  // Definition (siehe action-form-shim.ts für die Schulden-Doku). Die
@@ -2476,8 +2482,10 @@ function ActionFormBody({
2476
2482
  // Author entscheidet bewusst ob "stay on form" (default) oder
2477
2483
  // "back to list" (typisch bei Create-style Aktionen).
2478
2484
  if (screen.redirect !== undefined) {
2479
- const targetId = lastSegment(screen.redirect);
2480
- const targetFeatureName = featureNameFromQualifiedScreenId(screen.redirect);
2485
+ const redirectScreen = redirectScreenTarget(screen.redirect);
2486
+ const idField = typeof screen.redirect === "string" ? "id" : screen.redirect.idFrom;
2487
+ const targetId = lastSegment(redirectScreen);
2488
+ const targetFeatureName = featureNameFromQualifiedScreenId(redirectScreen);
2481
2489
  // A qualified redirect target may live in a different feature than
2482
2490
  // this screen's own schema (fw#2485) — resolve over all mounted
2483
2491
  // features (own schema first, cheap and provider-independent) same
@@ -2496,7 +2504,7 @@ function ActionFormBody({
2496
2504
  // pick by — fall back to the pre-fw#2485 best-effort match by
2497
2505
  // short id across all mounted features.
2498
2506
  appFeatures.flatMap((f) => f.screens).find((s) => lastSegment(s.id) === targetId));
2499
- const entityId = extractCreatedId(result.data);
2507
+ const entityId = extractIdField(result.data, idField);
2500
2508
  const carriesId =
2501
2509
  target !== undefined &&
2502
2510
  (target.type === "entityEdit" || target.type === "projectionDetail");
@@ -2514,7 +2522,9 @@ function ActionFormBody({
2514
2522
  // (Single-Action-Screens, wo Cancel nur Submit-ohne-Senden wäre).
2515
2523
  const handleCancel = useMemo<(() => void) | undefined>(() => {
2516
2524
  if (onCancelOverride !== undefined) return onCancelOverride;
2517
- const target = screen.cancelTarget ?? screen.redirect;
2525
+ const target =
2526
+ screen.cancelTarget ??
2527
+ (screen.redirect !== undefined ? redirectScreenTarget(screen.redirect) : undefined);
2518
2528
  if (target === undefined || target === false) return undefined;
2519
2529
  return () => nav.navigate({ screenId: lastSegment(target) });
2520
2530
  }, [nav, screen.redirect, screen.cancelTarget, onCancelOverride]);
@@ -23,10 +23,18 @@ function entityWriteCommand(featureName: string, entity: string): string {
23
23
  // The create handler's success payload is `{ kind: "save", id, ... }`
24
24
  // (see event-store-executor-write.ts) but RenderEdit's onSubmit only
25
25
  // types it as `unknown` — narrow defensively instead of casting through it.
26
- export function extractCreatedId(data: unknown): string | undefined {
26
+ // `field` is author-declared (ActionFormRedirect.idFrom, fw#2670), never
27
+ // user input; the own-property check keeps a prototype key like
28
+ // "constructor" from resolving to anything.
29
+ export function extractIdField(data: unknown, field: string): string | undefined {
27
30
  if (typeof data !== "object" || data === null) return undefined;
28
- const id = (data as Record<string, unknown>)["id"];
29
- return typeof id === "string" ? id : undefined;
31
+ if (!Object.hasOwn(data, field)) return undefined;
32
+ const value = (data as Record<string, unknown>)[field];
33
+ return typeof value === "string" ? value : undefined;
34
+ }
35
+
36
+ export function extractCreatedId(data: unknown): string | undefined {
37
+ return extractIdField(data, "id");
30
38
  }
31
39
 
32
40
  export type ReferenceCreateDialogProps = {