@cosmicdrift/kumiko-framework 0.330.0 → 0.330.2

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.
@@ -188,16 +188,6 @@ export function validatePiiAndRetention(feature) {
188
188
  // biome-ignore lint/suspicious/noConsole: boot-time dev hint, no logger available yet
189
189
  console.warn(`[kumiko:boot] [Feature ${feature.name}] Entity "${entityName}" retention.strategy="blockDelete" but no field has an anonymize-function. User-Forget cannot anonymize — Forget will return error. Add { anonymize: () => null } or () => "[ANONYMIZED]" to PII fields.`);
190
190
  }
191
- else if (!entityHasAnonymizableSubjectField) {
192
- const hasSubjectRef = Object.values(fieldsByName).some((f) => {
193
- const a = f; // @cast-boundary schema-walk
194
- return Boolean(a.subjectRef);
195
- });
196
- if (hasSubjectRef) {
197
- // biome-ignore lint/suspicious/noConsole: boot-time dev hint, no logger available yet
198
- console.warn(`[kumiko:boot] [Feature ${feature.name}] Entity "${entityName}" retention.strategy="blockDelete" with subjectRef-only PII relies on the EXT_USER_DATA delete hook for Art.17 — make sure it is not a no-op.`);
199
- }
200
- }
201
191
  }
202
192
  }
203
193
  }
@@ -67,6 +67,10 @@ function checkProjectionDetailOutputFields(queryHandlers, featureName, screenId,
67
67
  if (screen.header.status !== undefined)
68
68
  checkHeaderField("status", screen.header.status);
69
69
  }
70
+ if (screen.recordTitleField !== undefined) {
71
+ const field = screen.recordTitleField;
72
+ checkFieldExists(recordShape, field, () => `${prefix} recordTitleField references field "${field}" which is not present in query "${screen.query}"'s outputSchema.`);
73
+ }
70
74
  for (const metric of screen.metrics ?? []) {
71
75
  const field = metricField(metric);
72
76
  checkFieldExists(recordShape, field, () => `${prefix} metrics references field "${field}" which is not present in query "${screen.query}"'s outputSchema.`);
@@ -780,6 +780,10 @@ export function validateScreens(feature, featureMap, allWriteHandlerQns, allScre
780
780
  `and detailFor — the auto-generated "Edit" action navigates using the path's row id, which a ` +
781
781
  `singleton screen never has. Remove detailFor, or declare an explicit "edit" action instead.`);
782
782
  }
783
+ if (screen.recordTitleField !== undefined && screen.header !== undefined) {
784
+ throw new Error(`[Feature ${feature.name}] Screen "${screenId}" (projectionDetail) sets both header and ` +
785
+ `recordTitleField — header.title already names the record, so drop recordTitleField.`);
786
+ }
783
787
  if (screen.layout.sections.length === 0) {
784
788
  throw new Error(`[Feature ${feature.name}] Screen "${screenId}" (projectionDetail) has an empty sections list — ` +
785
789
  `declare at least one section.`);
@@ -54,6 +54,7 @@
54
54
  // buildAppSchema
55
55
  // - WorkspaceSchema.definition.nav — the raw explicit nav-QN list,
56
56
  // verbatim in JSON, distinct from the computed/filtered navMembers
57
+ // - entityList.createScreen (same-feature target, dropped when not kept)
57
58
  // - listScreenId (projectionDetail/entityEdit/actionForm/secretMint/custom —
58
59
  // the breadcrumb "back to list" target). It's serialized, so a visible
59
60
  // screen would otherwise disclose a denied screen's short id. Resolved
@@ -419,7 +420,16 @@ function projectCancelTarget(featureName, cancelTarget, keptScreenQns) {
419
420
  return cancelTarget;
420
421
  return isScreenTargetKept(featureName, cancelTarget, keptScreenQns) ? cancelTarget : undefined;
421
422
  }
422
- function projectListScreen(screen, feature, indices, keptScreenQns) {
423
+ function dropUnreachableCreateScreen(screen, feature, keptScreenQns) {
424
+ if (screen.type !== "entityList" || screen.createScreen === undefined)
425
+ return screen;
426
+ if (isScreenTargetKept(feature.featureName, screen.createScreen, keptScreenQns))
427
+ return screen;
428
+ const { createScreen: _unreachable, ...rest } = screen;
429
+ return rest;
430
+ }
431
+ function projectListScreen(original, feature, indices, keptScreenQns) {
432
+ const screen = dropUnreachableCreateScreen(original, feature, keptScreenQns);
423
433
  const { rowActions, toolbarActions, ...rest } = screen;
424
434
  const nextRowActions = projectRowActionArray(rowActions, feature, indices, keptScreenQns);
425
435
  const nextToolbarActions = projectToolbarActionArray(toolbarActions, feature, indices, keptScreenQns);
@@ -23,7 +23,11 @@ export function resolveNavParentScreen(screens, detail, getId) {
23
23
  // rowActions, toolbarActions, and drawer-kind actions (which mount inline
24
24
  // and have no page of their own) all count as "reached from" this list.
25
25
  const navigatesToDetail = (actions) => (actions ?? []).some((a) => (a.kind === "navigate" || a.kind === "drawer") && a.screen === detailScreenId);
26
+ // The create button of an entityList navigates to its createScreen, so the
27
+ // list is that screen's parent just like for a rowAction target.
26
28
  const listFromRowAction = screens.find((s) => {
29
+ if (s.type === "entityList" && s.createScreen === detailScreenId)
30
+ return true;
27
31
  if (s.type !== "entityList" && s.type !== "projectionList")
28
32
  return false;
29
33
  return navigatesToDetail(s.rowActions) || navigatesToDetail(s.toolbarActions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-framework",
3
- "version": "0.330.0",
3
+ "version": "0.330.2",
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>",
@@ -206,8 +206,8 @@
206
206
  "./package.json": "./package.json"
207
207
  },
208
208
  "dependencies": {
209
- "@cosmicdrift/kumiko-http": "0.330.0",
210
- "@cosmicdrift/kumiko-types": "0.330.0",
209
+ "@cosmicdrift/kumiko-http": "0.330.2",
210
+ "@cosmicdrift/kumiko-types": "0.330.2",
211
211
  "bullmq": "^5.76.7",
212
212
  "bun-types": "^1.3.13",
213
213
  "hono": "^4.13.1",
@@ -223,7 +223,7 @@
223
223
  "zod": "^4.4.3"
224
224
  },
225
225
  "devDependencies": {
226
- "@cosmicdrift/kumiko-dispatcher-live": "0.330.0",
226
+ "@cosmicdrift/kumiko-dispatcher-live": "0.330.2",
227
227
  "bun-types": "^1.3.13",
228
228
  "pino-pretty": "^13.1.3"
229
229
  },
package/src/changes.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.330.2",
4
+ "type": "fix",
5
+ "title": "createScreen role projection without a type assertion (no behaviour change)",
6
+ "detail": "Internal cleanup of the createScreen role projection added in the previous release; behaviour is unchanged."
7
+ },
2
8
  {
3
9
  "version": "0.330.0",
4
10
  "type": "improvement",