@cosmicdrift/kumiko-framework 0.284.0 → 0.285.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-framework",
3
- "version": "0.284.0",
3
+ "version": "0.285.1",
4
4
  "description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -198,8 +198,8 @@
198
198
  "./package.json": "./package.json"
199
199
  },
200
200
  "dependencies": {
201
- "@cosmicdrift/kumiko-http": "0.284.0",
202
- "@cosmicdrift/kumiko-types": "0.284.0",
201
+ "@cosmicdrift/kumiko-http": "0.285.1",
202
+ "@cosmicdrift/kumiko-types": "0.285.1",
203
203
  "bullmq": "^5.76.7",
204
204
  "bun-types": "^1.3.13",
205
205
  "hono": "^4.13.1",
@@ -215,7 +215,7 @@
215
215
  "zod": "^4.4.3"
216
216
  },
217
217
  "devDependencies": {
218
- "@cosmicdrift/kumiko-dispatcher-live": "0.284.0",
218
+ "@cosmicdrift/kumiko-dispatcher-live": "0.285.1",
219
219
  "bun-types": "^1.3.13",
220
220
  "pino-pretty": "^13.1.3"
221
221
  },
package/src/changes.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.285.0",
4
+ "type": "improvement",
5
+ "title": "Add a `tooling` repo kind, scoped out of guards by default",
6
+ "detail": "`kumiko-repo-manifest`'s `repoKindSchema` gains a `\"tooling\"` value for a repo with no product code (infra, Pulumi, build tooling). `kumiko-guards`' `scanRoots` treats an omitted `ScanSpec.kinds` as \"every root except tooling\" instead of \"every root\" — only 7 of ~60 guards declare `kinds` today, so without this a tooling repo would have been silently pulled into every product-oriented guard; a guard now has to name `\"tooling\"` in `kinds` explicitly to scan one. Separately, `kumiko-framework`'s boot-validator now includes the implicit parent-id field in a form screen's `urlPrefillFields` when it's reached via a relatedList toolbarAction that declares no `params` of its own — the renderer already sends that field (`parentFilter.field`, else `parentParam`, else `\"id\"`), the boot-validator just wasn't allowlisting it."
7
+ },
2
8
  {
3
9
  "version": "0.283.0",
4
10
  "type": "improvement",
@@ -93,6 +93,21 @@ const leasesFeature = defineFeature("leases", (r) => {
93
93
  },
94
94
  ],
95
95
  },
96
+ {
97
+ kind: "relatedList",
98
+ title: "Custom-key items",
99
+ query: "leases:query:lease:items",
100
+ columns: ["name"],
101
+ parentParam: "customParentId",
102
+ toolbarActions: [
103
+ {
104
+ kind: "navigate",
105
+ id: "silent-add-custom",
106
+ label: "Silent add",
107
+ screen: "item-toolbar-custom-parent",
108
+ },
109
+ ],
110
+ },
96
111
  ],
97
112
  },
98
113
  metrics: [
@@ -121,6 +136,12 @@ const leasesFeature = defineFeature("leases", (r) => {
121
136
  entity: "item",
122
137
  layout: { sections: [{ title: "x", fields: ["leaseId", "note"] }] },
123
138
  });
139
+ r.screen({
140
+ id: "item-toolbar-custom-parent",
141
+ type: "entityEdit",
142
+ entity: "item",
143
+ layout: { sections: [{ title: "x", fields: ["leaseId", "note"] }] },
144
+ });
124
145
  r.screen({
125
146
  id: "record-payment",
126
147
  type: "actionForm",
@@ -191,7 +212,11 @@ describe("buildAppSchema — urlPrefillFields derived from navigate params", ()
191
212
  expect(urlPrefillFieldsOf("leases", "item-toolbar-note")).toEqual(["leaseId"]);
192
213
  });
193
214
 
194
- test("a relatedList toolbarAction without params contributes no fields — the allowlist stays an allowlist", () => {
195
- expect(urlPrefillFieldsOf("leases", "item-toolbar-silent")).toEqual([]);
215
+ test("a relatedList toolbarAction without params still contributes the implicit parent-id field", () => {
216
+ expect(urlPrefillFieldsOf("leases", "item-toolbar-silent")).toEqual(["id"]);
217
+ });
218
+
219
+ test("a relatedList toolbarAction without params uses the section's own parentParam key", () => {
220
+ expect(urlPrefillFieldsOf("leases", "item-toolbar-custom-parent")).toEqual(["customParentId"]);
196
221
  });
197
222
  });
@@ -1,6 +1,7 @@
1
1
  import { qualifyEntityName } from "../qualified-name";
2
2
  import type { FeatureDefinition } from "../types";
3
3
  import type {
4
+ EditRelatedListSection,
4
5
  ProjectionDetailScreenDefinition,
5
6
  RowFieldExtractor,
6
7
  ScreenDefinition,
@@ -26,23 +27,29 @@ type NavigateActionLike = {
26
27
  readonly entityId?: string;
27
28
  readonly params?: RowFieldExtractor;
28
29
  };
29
- type NavigateActionWithParams = NavigateActionLike & { readonly params: RowFieldExtractor };
30
30
  type ScopedNavigateAction = {
31
31
  readonly action: NavigateActionLike;
32
32
  readonly sourceScreenEntity: string | undefined;
33
+ /** Used when the action itself declares no `params` — the field a
34
+ * relatedList toolbarAction's parent id lands under at runtime
35
+ * (RelatedListSection's `navigatePrefill`, related-list-section.tsx). */
36
+ readonly implicitParams?: RowFieldExtractor;
33
37
  };
34
38
 
35
39
  function isNavigateKind(action: NavigateActionLike): boolean {
36
40
  return action.kind === undefined || action.kind === "navigate";
37
41
  }
38
- function isNavigateWithParams(action: NavigateActionLike): action is NavigateActionWithParams {
39
- return isNavigateKind(action) && action.params !== undefined;
40
- }
41
42
  function scopeActions(
42
43
  actions: readonly NavigateActionLike[] | undefined,
43
44
  sourceScreenEntity: string | undefined,
45
+ implicitParams?: RowFieldExtractor,
44
46
  ): ScopedNavigateAction[] {
45
- return (actions ?? []).map((action) => ({ action, sourceScreenEntity }));
47
+ return (actions ?? []).map((action) => ({ action, sourceScreenEntity, implicitParams }));
48
+ }
49
+ // Same key the renderer falls back to when a relatedList toolbarAction has no
50
+ // `params` of its own: `parentFilter.field`, else `parentParam`, else "id".
51
+ function relatedListParentParamField(section: EditRelatedListSection): string {
52
+ return section.parentFilter?.field ?? section.parentParam ?? "id";
46
53
  }
47
54
  function projectionDetailNavigateActions(
48
55
  screen: ProjectionDetailScreenDefinition,
@@ -53,7 +60,11 @@ function projectionDetailNavigateActions(
53
60
  section.kind === "relatedList" ? scopeActions(section.rowActions, undefined) : [],
54
61
  ),
55
62
  ...screen.layout.sections.flatMap((section) =>
56
- section.kind === "relatedList" ? scopeActions(section.toolbarActions, undefined) : [],
63
+ section.kind === "relatedList"
64
+ ? scopeActions(section.toolbarActions, undefined, {
65
+ pick: [relatedListParentParamField(section)],
66
+ })
67
+ : [],
57
68
  ),
58
69
  ...(screen.metrics ?? []).flatMap((metric) =>
59
70
  typeof metric === "string" || metric.navigate === undefined
@@ -77,25 +88,30 @@ function navigateActionsOf(screen: ScreenDefinition): ScopedNavigateAction[] {
77
88
  }
78
89
  }
79
90
  function toNavigateParamsSource(
80
- action: NavigateActionWithParams,
91
+ action: NavigateActionLike,
92
+ params: RowFieldExtractor,
81
93
  sourceScreenEntity: string | undefined,
82
94
  ): NavigateParamsSource {
83
95
  return {
84
96
  ...(action.screen !== undefined && { screen: action.screen }),
85
97
  ...(action.entity !== undefined && { entity: action.entity }),
86
98
  ...(action.entityId !== undefined && { entityId: action.entityId }),
87
- params: action.params,
99
+ params,
88
100
  sourceScreenEntity,
89
101
  };
90
102
  }
91
103
 
92
- // Every declarative navigate that writes `params` into the target's URL —
104
+ // Every declarative navigate that writes params into the target's URL —
93
105
  // the same sources validateScreens pairs with validateRowActionNavigateParams,
94
- // plus projectionDetail metrics (runMetricNavigate), which reuse that shape.
106
+ // plus projectionDetail metrics (runMetricNavigate), which reuse that shape
107
+ // plus a relatedList toolbarAction with no `params` of its own, which still
108
+ // writes its implicit parent-id param at runtime (see `implicitParams`).
95
109
  function navigateParamsSources(screen: ScreenDefinition): NavigateParamsSource[] {
96
- return navigateActionsOf(screen).flatMap(({ action, sourceScreenEntity }) =>
97
- isNavigateWithParams(action) ? [toNavigateParamsSource(action, sourceScreenEntity)] : [],
98
- );
110
+ return navigateActionsOf(screen).flatMap(({ action, sourceScreenEntity, implicitParams }) => {
111
+ if (!isNavigateKind(action)) return [];
112
+ const params = action.params ?? implicitParams;
113
+ return params === undefined ? [] : [toNavigateParamsSource(action, params, sourceScreenEntity)];
114
+ });
99
115
  }
100
116
 
101
117
  // Per target screen QN, the union of field names any navigate `params` may