@cosmicdrift/kumiko-renderer 0.289.0 → 0.290.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.289.0",
3
+ "version": "0.290.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.289.0",
19
- "@cosmicdrift/kumiko-headless": "0.289.0",
20
- "@cosmicdrift/kumiko-types": "0.289.0",
18
+ "@cosmicdrift/kumiko-framework": "0.290.0",
19
+ "@cosmicdrift/kumiko-headless": "0.290.0",
20
+ "@cosmicdrift/kumiko-types": "0.290.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.289.0"
31
+ "@cosmicdrift/kumiko-locale-de": "0.290.0"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
@@ -1,5 +1,7 @@
1
1
  import { describe, expect, spyOn, test } from "bun:test";
2
+ import type { EntityEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
3
  import { mergeSearchParamsIntoInitial } from "../app/kumiko-screen";
4
+ import { layoutFieldNames } from "../app/layout-fields";
3
5
 
4
6
  type FieldDef = {
5
7
  type?: string;
@@ -188,6 +190,22 @@ describe("mergeSearchParamsIntoInitial — coercion (every field URL-prefillable
188
190
  expect(result["ownerId"]).toBe("");
189
191
  });
190
192
 
193
+ test("renderableFields from a groups-only layout: searchParam on a group field is kept", () => {
194
+ const screen: EntityEditScreenDefinition = {
195
+ id: "s",
196
+ type: "entityEdit",
197
+ entity: "e",
198
+ layout: { sections: [{ fields: [], groups: [{ title: "g", fields: ["status"] }] }] },
199
+ };
200
+ const fields: Record<string, FieldDef> = { status: { type: "text", default: "draft" } };
201
+ const result = mergeWithAllFieldsUrlPrefillable(
202
+ fields,
203
+ { status: "approved" },
204
+ layoutFieldNames(screen),
205
+ );
206
+ expect(result["status"]).toBe("approved");
207
+ });
208
+
191
209
  test("no renderableFields set given (undefined): behaves as before, all fields eligible", () => {
192
210
  const fields: Record<string, FieldDef> = { ownerId: { type: "text" } };
193
211
  const result = mergeWithAllFieldsUrlPrefillable(fields, { ownerId: "user-123" });
@@ -41,6 +41,21 @@ describe("buildFormSchema", () => {
41
41
  }
42
42
  });
43
43
 
44
+ test("required field inside a groups-only section → presence issue", () => {
45
+ const entity = entityWith({ name: { type: "text", required: true } });
46
+ const screen: EntityEditScreenDefinition = {
47
+ id: "s",
48
+ type: "entityEdit",
49
+ entity: "e",
50
+ layout: { sections: [{ fields: [], groups: [{ title: "g", fields: ["name"] }] }] },
51
+ };
52
+
53
+ const result = buildFormSchema(entity, screen).safeParse({ name: "" });
54
+ expect(result.success).toBe(false);
55
+ if (result.success) return;
56
+ expect(result.error.issues[0]?.path).toEqual(["name"]);
57
+ });
58
+
44
59
  test("required field missing → issue carries the required-field i18nKey override", () => {
45
60
  const entity = entityWith({ name: { type: "text", required: true } });
46
61
  const screen = screenWith(["name"]);
@@ -2,7 +2,11 @@ import type {
2
2
  EditFieldSpec,
3
3
  EntityEditScreenDefinition,
4
4
  } from "@cosmicdrift/kumiko-framework/ui-types";
5
- import { isFieldsEditSection, normalizeEditField } from "@cosmicdrift/kumiko-framework/ui-types";
5
+ import {
6
+ isFieldsEditSection,
7
+ normalizeEditField,
8
+ sectionFieldSpecs,
9
+ } from "@cosmicdrift/kumiko-framework/ui-types";
6
10
 
7
11
  // Normalized field specs actually rendered by the screen's layout, extension
8
12
  // (and relatedList) sections skipped. Both this and `layoutFieldNames` key
@@ -15,7 +19,7 @@ export function layoutEditFields(
15
19
  const specs: Exclude<EditFieldSpec, string>[] = [];
16
20
  for (const section of screen.layout.sections) {
17
21
  if (!isFieldsEditSection(section)) continue;
18
- for (const spec of section.fields) {
22
+ for (const spec of sectionFieldSpecs(section)) {
19
23
  specs.push(normalizeEditField(spec));
20
24
  }
21
25
  }
@@ -22,7 +22,6 @@
22
22
 
23
23
  import type {
24
24
  EditFieldSpec,
25
- EditFieldsSection,
26
25
  EditLayout,
27
26
  EntityDefinition,
28
27
  EntityEditScreenDefinition,
@@ -32,18 +31,9 @@ import {
32
31
  isFieldsEditSection,
33
32
  normalizeEditField,
34
33
  PROJECTION_DETAIL_ENTITY as PROJECTION_DETAIL_PSEUDO_ENTITY,
34
+ sectionFieldSpecs,
35
35
  } from "@cosmicdrift/kumiko-framework/ui-types";
36
36
 
37
- // The boot-validator treats `fields`/`groups` as mutually exclusive per
38
- // section, but this reads both anyway: an extra name the current section
39
- // shape can never carry is harmless, while a name computeEditViewModel does
40
- // reference and this shim omitted throws downstream ("references unknown
41
- // field ... on entity"). Union is strictly safer than picking one branch.
42
- function allFieldSpecs(section: EditFieldsSection): readonly EditFieldSpec[] {
43
- const grouped = section.groups?.flatMap((group) => group.fields) ?? [];
44
- return [...section.fields, ...grouped];
45
- }
46
-
47
37
  /** Minimale EntityDefinition aus den Layout-Feldern: jedes Feld ein Text-
48
38
  * Feld — computeEditViewModel liest nur `fields[<f>].type`, Text reicht für
49
39
  * eine reine Anzeige (kein Select/Number-spezifisches Rendering nötig). */
@@ -52,7 +42,7 @@ export function synthesizeProjectionDetailEntity(layout: EditLayout): EntityDefi
52
42
  for (const section of layout.sections) {
53
43
  // relatedList and extension sections carry no `fields` to synthesize.
54
44
  if (!isFieldsEditSection(section)) continue;
55
- for (const spec of allFieldSpecs(section)) {
45
+ for (const spec of sectionFieldSpecs(section)) {
56
46
  fields[normalizeEditField(spec).field] = { type: "text" };
57
47
  }
58
48
  }
package/src/changes.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.290.0",
4
+ "type": "fix",
5
+ "title": "Layout field readers see groups[].fields",
6
+ "detail": "layoutEditFields flattens section.groups, so required fields inside a groups-only section are presence-validated by buildFormSchema and survive the renderableFields gate for search-param prefills."
7
+ },
2
8
  {
3
9
  "version": "0.283.0",
4
10
  "type": "fix",