@cosmicdrift/kumiko-renderer 0.209.1 → 0.211.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.209.1",
3
+ "version": "0.211.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.209.1",
19
- "@cosmicdrift/kumiko-headless": "0.209.1",
18
+ "@cosmicdrift/kumiko-framework": "0.211.0",
19
+ "@cosmicdrift/kumiko-headless": "0.211.0",
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.209.1"
28
+ "@cosmicdrift/kumiko-locale-de": "0.211.0"
29
29
  },
30
30
  "repository": {
31
31
  "type": "git",
@@ -0,0 +1,162 @@
1
+ // fw-i18n-funde: user-data-rights's export-job-list screen (bundled-features/
2
+ // src/user-data-rights/screens.ts) ships a `navigate` row action labelled
3
+ // "kumiko.actions.view", following the same `kumiko.actions.*` convention as
4
+ // tenant/screens.ts and user/screens.ts (e.g. "kumiko.actions.edit"). Unlike
5
+ // those, "kumiko.actions.view" was never declared in the framework's default
6
+ // bundle (renderer/src/i18n-defaults.ts) — every mounting app rendered the
7
+ // raw key instead of a translated label. This renders the real entityList
8
+ // pipeline (KumikoScreen → EntityListBody) against the framework's own
9
+ // kumikoDefaultTranslations, mirroring entity-list-row-action-refetch.test.tsx's
10
+ // harness, and asserts the row action resolves to actual text.
11
+
12
+ import { describe, expect, test } from "bun:test";
13
+ import type {
14
+ EntityDefinition,
15
+ EntityListScreenDefinition,
16
+ } from "@cosmicdrift/kumiko-framework/ui-types";
17
+ import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
18
+ import { render, waitFor } from "@testing-library/react";
19
+ import type { ComponentType, ReactNode } from "react";
20
+ import { DispatcherProvider } from "../../context/dispatcher-context";
21
+ import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
22
+ import { kumikoDefaultTranslations } from "../../i18n-defaults";
23
+ import {
24
+ type CorePrimitives,
25
+ type DataTableProps,
26
+ type DataTableRowAction,
27
+ PrimitivesProvider,
28
+ } from "../../primitives";
29
+ import type { FeatureSchema } from "../feature-schema";
30
+ import { KumikoScreen } from "../kumiko-screen";
31
+ import { NavProvider } from "../nav";
32
+
33
+ function stubDispatcher(): Dispatcher {
34
+ return {
35
+ write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
36
+ query: (async () => ({
37
+ isSuccess: true,
38
+ data: { rows: [{ id: "job-1", status: "completed" }], nextCursor: null, total: 1 },
39
+ })) as unknown as Dispatcher["query"],
40
+ batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
41
+ statusStore: {
42
+ getState: () => "online",
43
+ subscribe: () => () => {},
44
+ } as unknown as Dispatcher["statusStore"],
45
+ async *stream() {},
46
+ pendingWrites: () => [],
47
+ pendingFiles: () => [],
48
+ };
49
+ }
50
+
51
+ let capturedRowActions: readonly DataTableRowAction[] | undefined;
52
+ const captureDataTable: ComponentType<DataTableProps> = (props) => {
53
+ capturedRowActions = props.rowActions;
54
+ return null;
55
+ };
56
+ const noop = (): ReactNode => null;
57
+ const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
58
+
59
+ const testPrimitives: CorePrimitives = {
60
+ Button: noop,
61
+ Banner: passChildren,
62
+ Field: passChildren,
63
+ Input: noop,
64
+ DataTable: captureDataTable,
65
+ Form: passChildren,
66
+ Section: passChildren,
67
+ Card: passChildren,
68
+ Grid: passChildren,
69
+ GridCell: passChildren,
70
+ Text: passChildren,
71
+ Heading: noop,
72
+ Dialog: noop,
73
+ Modal: noop,
74
+ Lightbox: noop,
75
+ ConfigSourceBadge: noop,
76
+ ConfigCascadeView: noop,
77
+ Link: noop,
78
+ };
79
+
80
+ function buildSchema(): FeatureSchema {
81
+ const entity: EntityDefinition = {
82
+ fields: {
83
+ status: {
84
+ type: "text",
85
+ maxLength: 50,
86
+ required: false,
87
+ searchable: false,
88
+ sortable: false,
89
+ },
90
+ },
91
+ };
92
+ // Mirrors user-data-rights's export-job-list rowAction verbatim (kind,
93
+ // label key, entityId) — only ids/entity/screen are renamed for the fixture.
94
+ const listScreen: EntityListScreenDefinition = {
95
+ id: "export-job-list",
96
+ type: "entityList",
97
+ entity: "export-job",
98
+ columns: ["status"],
99
+ rowActions: [
100
+ {
101
+ kind: "navigate",
102
+ id: "view",
103
+ label: "kumiko.actions.view",
104
+ screen: "export-job-detail",
105
+ entityId: "id",
106
+ },
107
+ ],
108
+ };
109
+ return {
110
+ featureName: "user-data-rights",
111
+ entities: { "export-job": entity },
112
+ screens: [listScreen],
113
+ } as FeatureSchema;
114
+ }
115
+
116
+ function renderListScreen(): void {
117
+ render(
118
+ <LocaleProvider
119
+ resolver={createStaticLocaleResolver({ locale: "en" })}
120
+ fallbackBundles={[kumikoDefaultTranslations]}
121
+ >
122
+ <DispatcherProvider dispatcher={stubDispatcher()}>
123
+ <NavProvider
124
+ value={{
125
+ route: { screenId: "user-data-rights:export-job-list" },
126
+ navigate: () => {},
127
+ replace: () => {},
128
+ hrefFor: () => "",
129
+ searchParams: {},
130
+ setSearchParams: () => {},
131
+ }}
132
+ >
133
+ <PrimitivesProvider value={testPrimitives}>
134
+ <KumikoScreen schema={buildSchema()} qn="user-data-rights:screen:export-job-list" />
135
+ </PrimitivesProvider>
136
+ </NavProvider>
137
+ </DispatcherProvider>
138
+ </LocaleProvider>,
139
+ );
140
+ }
141
+
142
+ function requireViewAction(): DataTableRowAction {
143
+ const action = capturedRowActions?.find((a) => a.id === "view");
144
+ if (!action) throw new Error("expected the 'view' row action to be captured");
145
+ return action;
146
+ }
147
+
148
+ describe("entityList navigate row-action renders the framework's kumiko.actions.* labels", () => {
149
+ test("kumiko.actions.view resolves to real text, not the raw key", async () => {
150
+ capturedRowActions = undefined;
151
+
152
+ renderListScreen();
153
+
154
+ await waitFor(() => {
155
+ expect(capturedRowActions).toBeDefined();
156
+ });
157
+
158
+ const viewAction = requireViewAction();
159
+ expect(viewAction.label).not.toBe("kumiko.actions.view");
160
+ expect(viewAction.label).toBe("View");
161
+ });
162
+ });
@@ -1910,11 +1910,6 @@ function ProjectionDetailBody({
1910
1910
  entityId !== undefined ? { [idParam]: entityId } : {},
1911
1911
  );
1912
1912
 
1913
- const navigateToList = useCallback(() => {
1914
- if (screen.listScreenId === undefined) return;
1915
- nav.navigate({ screenId: screen.listScreenId });
1916
- }, [nav, screen.listScreenId]);
1917
-
1918
1913
  // Default edit action (fw#2166): resolved cross-feature over ALL mounted
1919
1914
  // features, not just this feature's own schema — detailFor itself is
1920
1915
  // resolved cross-feature by the boot-validator (detail-screens.ts), and
@@ -2091,11 +2086,9 @@ function ProjectionDetailBody({
2091
2086
  initial={record as FormValues}
2092
2087
  entityId={entityId}
2093
2088
  customSubmit={async () => ({ isSuccess: true, validationBlocked: false, data: undefined })}
2094
- onCancel={screen.listScreenId !== undefined ? navigateToList : undefined}
2095
2089
  {...(headerActions !== undefined && { actions: headerActions })}
2096
2090
  {...(translate !== undefined && { translate })}
2097
2091
  valueDisplay={screen.valueDisplay ?? "text"}
2098
- hideActions={screen.hideActions === true}
2099
2092
  />
2100
2093
  );
2101
2094
  }
@@ -19,6 +19,7 @@ export const kumikoDefaultTranslations: TranslationsByLocale = {
19
19
  "kumiko.actions.reload": "Reload",
20
20
  "kumiko.actions.create": "New",
21
21
  "kumiko.actions.edit": "Edit",
22
+ "kumiko.actions.view": "View",
22
23
  "kumiko.actions.copyLink": "Copy link",
23
24
  "kumiko.actions.copyLinkCopied": "Copied!",
24
25
  "kumiko.actions.next": "Next",