@cosmicdrift/kumiko-renderer 0.223.0 → 0.224.1

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.223.0",
3
+ "version": "0.224.1",
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.223.0",
19
- "@cosmicdrift/kumiko-headless": "0.223.0",
18
+ "@cosmicdrift/kumiko-framework": "0.224.1",
19
+ "@cosmicdrift/kumiko-headless": "0.224.1",
20
20
  "react": "^19.2.6",
21
21
  "temporal-polyfill": "^0.3.2",
22
22
  "zod": "^4.4.3"
@@ -25,7 +25,7 @@
25
25
  "@testing-library/react": "^16.3.2",
26
26
  "@types/react": "^19.2.14",
27
27
  "jsdom": "^29.1.1",
28
- "@cosmicdrift/kumiko-locale-de": "0.223.0"
28
+ "@cosmicdrift/kumiko-locale-de": "0.224.1"
29
29
  },
30
30
  "repository": {
31
31
  "type": "git",
@@ -51,7 +51,7 @@ import {
51
51
  } from "./projection-detail-shim";
52
52
  import { synthesizeProjectionEntity, synthesizeProjectionScreen } from "./projection-list-shim";
53
53
  import { lastSegment, toKebab } from "./qn";
54
- import { qualifyScreenId } from "./qualify-screen-id";
54
+ import { featureNameFromQualifiedScreenId, qualifyScreenId } from "./qualify-screen-id";
55
55
  import { screenAccessAllows } from "./screen-access";
56
56
  import { dispatcherErrorText, WriteFailedError } from "./write-failed-error";
57
57
 
@@ -2264,6 +2264,7 @@ function ActionFormBody({
2264
2264
  readonly onCancelOverride?: () => void;
2265
2265
  }): ReactNode {
2266
2266
  const nav = useNav();
2267
+ const appFeatures = useAppFeatures();
2267
2268
  const synthEntity = useMemo(() => synthesizeActionFormEntity(screen.fields), [screen.fields]);
2268
2269
  const synthScreen = useMemo(() => synthesizeActionFormScreen(screen), [screen]);
2269
2270
  const initial = useMemo(
@@ -2287,7 +2288,25 @@ function ActionFormBody({
2287
2288
  // "back to list" (typisch bei Create-style Aktionen).
2288
2289
  if (screen.redirect !== undefined) {
2289
2290
  const targetId = lastSegment(screen.redirect);
2290
- const target = schema.screens.find((s) => lastSegment(s.id) === targetId);
2291
+ const targetFeatureName = featureNameFromQualifiedScreenId(screen.redirect);
2292
+ // A qualified redirect target may live in a different feature than
2293
+ // this screen's own schema (fw#2485) — resolve over all mounted
2294
+ // features (own schema first, cheap and provider-independent) same
2295
+ // as ProjectionDetailBody's cross-feature editScreen lookup above.
2296
+ // A screen id is only unique WITHIN a feature, so once the QN names
2297
+ // a feature, the fallback must look inside THAT feature, not just
2298
+ // take the first short-id match across every mounted feature — two
2299
+ // features can easily share an id like "list" or "edit".
2300
+ const target =
2301
+ schema.screens.find((s) => lastSegment(s.id) === targetId) ??
2302
+ (targetFeatureName !== undefined
2303
+ ? appFeatures
2304
+ .find((f) => f.featureName === targetFeatureName)
2305
+ ?.screens.find((s) => lastSegment(s.id) === targetId)
2306
+ : // Bare short-id redirect (no feature prefix) names no feature to
2307
+ // pick by — fall back to the pre-fw#2485 best-effort match by
2308
+ // short id across all mounted features.
2309
+ appFeatures.flatMap((f) => f.screens).find((s) => lastSegment(s.id) === targetId));
2291
2310
  const entityId = extractCreatedId(result.data);
2292
2311
  const carriesId =
2293
2312
  target !== undefined &&
@@ -2298,7 +2317,7 @@ function ActionFormBody({
2298
2317
  });
2299
2318
  }
2300
2319
  },
2301
- [nav, screen.redirect, onSuccess, schema.screens],
2320
+ [nav, screen.redirect, onSuccess, schema.screens, appFeatures],
2302
2321
  );
2303
2322
  // Cancel ist nur sinnvoll wenn ein Navigations-Ziel existiert —
2304
2323
  // sonst hätte der Button nirgendwo hin zu navigieren. cancelTarget
@@ -4,3 +4,12 @@
4
4
  export function qualifyScreenId(featureName: string, screenId: string): string {
5
5
  return `${featureName}:screen:${screenId}`;
6
6
  }
7
+
8
+ // Inverse of qualifyScreenId. A short id ("task-list") has no feature
9
+ // segment and returns undefined — callers need that to tell "this redirect
10
+ // names a feature" apart from "this redirect is feature-less, resolve by
11
+ // short id alone" (fw#2485).
12
+ export function featureNameFromQualifiedScreenId(id: string): string | undefined {
13
+ const parts = id.split(":");
14
+ return parts.length === 3 && parts[1] === "screen" ? parts[0] : undefined;
15
+ }