@cosmicdrift/kumiko-renderer-web 0.97.0 → 0.98.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-web",
3
- "version": "0.97.0",
3
+ "version": "0.98.0",
4
4
  "description": "Web-platform bindings for @cosmicdrift/kumiko-renderer. HTML default-primitives, browser history-based navigation, EventSource-backed live events, and a one-call createKumikoApp that mounts the whole stack via react-dom.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -16,9 +16,9 @@
16
16
  "./styles.css": "./src/styles.css"
17
17
  },
18
18
  "dependencies": {
19
- "@cosmicdrift/kumiko-dispatcher-live": "0.97.0",
20
- "@cosmicdrift/kumiko-headless": "0.97.0",
21
- "@cosmicdrift/kumiko-renderer": "0.97.0",
19
+ "@cosmicdrift/kumiko-dispatcher-live": "0.98.0",
20
+ "@cosmicdrift/kumiko-headless": "0.98.0",
21
+ "@cosmicdrift/kumiko-renderer": "0.98.0",
22
22
  "@radix-ui/react-dialog": "^1.1.15",
23
23
  "@radix-ui/react-dropdown-menu": "^2.1.16",
24
24
  "@radix-ui/react-label": "^2.1.8",
@@ -6,12 +6,13 @@ import type {
6
6
  EntityListScreenDefinition,
7
7
  } from "@cosmicdrift/kumiko-framework/ui-types";
8
8
  import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
9
- import type { FeatureSchema } from "@cosmicdrift/kumiko-renderer";
9
+ import type { FeatureSchema, NavApi } from "@cosmicdrift/kumiko-renderer";
10
10
  import {
11
11
  DispatcherProvider,
12
12
  ExtensionSectionsProvider,
13
13
  KumikoScreen,
14
14
  kumikoDefaultTranslations,
15
+ NavProvider,
15
16
  } from "@cosmicdrift/kumiko-renderer";
16
17
  import userEvent from "@testing-library/user-event";
17
18
  import { createMockDispatcher, fireEvent, render, screen, waitFor } from "./test-utils";
@@ -1055,6 +1056,42 @@ describe("KumikoScreen", () => {
1055
1056
  expect(payload.filter).toEqual({ field: "status", op: "eq", value: "scheduled" });
1056
1057
  });
1057
1058
 
1059
+ // TagFilter drop-in (core edit): a faceted filter on `id` — a base column NOT
1060
+ // in entity.fields — must still pass through as an op:"in" id-set so a header-
1061
+ // slot control can narrow ANY list to a resolved set of row ids. Before the
1062
+ // edit, `id` was silently dropped (only entity.fields facets survived).
1063
+ test("entityList faceted id-filter → payload.filters carries an op:'in' id-set", async () => {
1064
+ const queryCalls: { type: string; payload: unknown }[] = [];
1065
+ const dispatcher = makeDispatcher({
1066
+ query: (async (type: string, payload: unknown) => {
1067
+ queryCalls.push({ type, payload });
1068
+ return { isSuccess: true, data: { rows: [], nextCursor: null } };
1069
+ }) as unknown as Dispatcher["query"],
1070
+ });
1071
+ // useListUrlState reads `<screenId>.f.<field>` (comma-joined) from nav —
1072
+ // listScreen.id is "task-list", so this seeds an id-facet of two row ids.
1073
+ const navWithIdFilter: NavApi = {
1074
+ route: undefined,
1075
+ navigate: () => {},
1076
+ replace: () => {},
1077
+ hrefFor: (t) => `/${t.screenId}`,
1078
+ searchParams: { "task-list.f.id": "r1,r2" },
1079
+ setSearchParams: () => {},
1080
+ };
1081
+
1082
+ render(
1083
+ <NavProvider value={navWithIdFilter}>
1084
+ <DispatcherProvider dispatcher={dispatcher}>
1085
+ <KumikoScreen schema={schema} qn="tasks:screen:task-list" />
1086
+ </DispatcherProvider>
1087
+ </NavProvider>,
1088
+ );
1089
+ await waitFor(() => expect(queryCalls.length).toBeGreaterThan(0));
1090
+
1091
+ const payload = queryCalls[0]?.payload as { filters?: unknown };
1092
+ expect(payload.filters).toEqual([{ field: "id", op: "in", value: ["r1", "r2"] }]);
1093
+ });
1094
+
1058
1095
  // Regression-Guard: Default-Pfad (kein screen.filter) darf KEIN
1059
1096
  // filter-Feld in den queryPayload schicken. Sonst würde Zod-Strict
1060
1097
  // ein leeres `filter: undefined` als 400 abweisen, oder ein
@@ -153,7 +153,8 @@ describe("RenderList", () => {
153
153
  describe("RenderList — slots.header", () => {
154
154
  const ListHeader = (props: ExtensionSectionProps) => (
155
155
  <div data-testid="list-header-slot">
156
- header for {props.entityName} (id={String(props.entityId)})
156
+ header for {props.entityName} (id={String(props.entityId)}) (screen=
157
+ {String(props.screenId)})
157
158
  </div>
158
159
  );
159
160
  const screenWithHeader: EntityListScreenDefinition = {
@@ -171,6 +172,9 @@ describe("RenderList — slots.header", () => {
171
172
  expect(header.textContent).toContain("header for task");
172
173
  // Listen-Kontext → keine Row → entityId null.
173
174
  expect(header.textContent).toContain("id=null");
175
+ // Core edit: the header slot gets THIS list's screenId so a control like
176
+ // the tags TagFilter can drive its url-filter state (useListUrlState).
177
+ expect(header.textContent).toContain(`screen=${screenWithHeader.id}`);
174
178
  // Placement-Regression (Bug-Bash 3 #12): der Header-Slot lebt IM
175
179
  // Toolbar-Container (toolbarEnd), NICHT als loser Node über dem
176
180
  // Screen-Titel.