@cosmicdrift/kumiko-framework 0.255.2 → 0.257.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-framework",
3
- "version": "0.255.2",
3
+ "version": "0.257.0",
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,7 +198,7 @@
198
198
  "./package.json": "./package.json"
199
199
  },
200
200
  "dependencies": {
201
- "@cosmicdrift/kumiko-types": "0.255.2",
201
+ "@cosmicdrift/kumiko-types": "0.257.0",
202
202
  "bullmq": "^5.76.7",
203
203
  "bun-types": "^1.3.13",
204
204
  "hono": "^4.13.1",
@@ -214,7 +214,7 @@
214
214
  "zod": "^4.4.3"
215
215
  },
216
216
  "devDependencies": {
217
- "@cosmicdrift/kumiko-dispatcher-live": "0.255.2",
217
+ "@cosmicdrift/kumiko-dispatcher-live": "0.257.0",
218
218
  "bun-types": "^1.3.13",
219
219
  "pino-pretty": "^13.1.3"
220
220
  },
@@ -189,6 +189,10 @@ describe("buildAppSchema — Settings-Hub inline placement", () => {
189
189
  const ok = defineFeature("ok", (r) => {
190
190
  r.config({ keys: { fee: createSystemConfig("number", { mask: { title: "ok.fee" } }) } });
191
191
  r.workspace({ id: "w", label: "W", nav: ["config:nav:audience-system"] });
192
+ // mask.title is unconditionally required (fw#2313) — irrelevant to what
193
+ // this test actually asserts (audience-nav QN exemption), but boot
194
+ // still needs it translated.
195
+ r.translations({ keys: { "ok.fee": { en: "Fee" } } });
192
196
  });
193
197
  expect(() => validateBoot([ok])).not.toThrow();
194
198
 
@@ -8,6 +8,7 @@ import {
8
8
  requiredKeysFromWorkspace,
9
9
  screenTitleKey,
10
10
  } from "../../i18n/required-surface-keys";
11
+ import { i18nKey } from "../i18n-key";
11
12
  import type {
12
13
  ConfigEditScreenDefinition,
13
14
  EntityEditScreenDefinition,
@@ -216,3 +217,49 @@ describe("dot-form labels + treatDotFormAsKey (fw#2260)", () => {
216
217
  expect(requiredKeysFromNav(nav, { treatDotFormAsKey: true })).toContain("billing.settings");
217
218
  });
218
219
  });
220
+
221
+ // fw#2313: a hand-written dot-form label ("sessions.list.col.id") is
222
+ // indistinguishable from literal display text ("actions.open") — isI18nKey
223
+ // only recognizes it once the author marks it explicitly via i18nKey().
224
+ describe("dot-form label opt-in (fw#2313)", () => {
225
+ test("unmarked dot-form label is not required (regression guard for literal display text)", () => {
226
+ const screen: EntityListScreenDefinition = {
227
+ id: "fw2313-list",
228
+ type: "entityList",
229
+ entity: "widget",
230
+ columns: ["name"],
231
+ rowActions: [
232
+ {
233
+ id: "open",
234
+ label: "actions.open",
235
+ handler: "fw2313:write:widget:open",
236
+ },
237
+ ],
238
+ };
239
+ const keys = requiredKeysFromScreen("fw2313", screen);
240
+ expect(keys).not.toContain("actions.open");
241
+ });
242
+
243
+ test("i18nKey()-marked dot-form label is required", () => {
244
+ const screen: EntityListScreenDefinition = {
245
+ id: "fw2313-list",
246
+ type: "entityList",
247
+ entity: "widget",
248
+ columns: ["name"],
249
+ rowActions: [
250
+ {
251
+ id: "open",
252
+ label: i18nKey("fw2313.optin.col.alpha"),
253
+ handler: "fw2313:write:widget:open",
254
+ },
255
+ ],
256
+ };
257
+ const keys = requiredKeysFromScreen("fw2313", screen);
258
+ expect(keys).toContain("fw2313.optin.col.alpha");
259
+ });
260
+
261
+ test("i18nKey()-marked nav label is required", () => {
262
+ const nav = { id: "fw2313-nav", label: i18nKey("fw2313.optin.nav.beta") };
263
+ expect(requiredKeysFromNav(nav)).toContain("fw2313.optin.nav.beta");
264
+ });
265
+ });
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
  import { buildConfigFeatureSchema } from "../../build-config-feature-schema";
3
3
  import { access, createTenantConfig } from "../../config-helpers";
4
4
  import { defineFeature } from "../../define-feature";
5
+ import { i18nKey } from "../../i18n-key";
5
6
  import { createRegistry } from "../../registry";
6
7
  import { isFieldsEditSection } from "../../screen-helpers";
7
8
  import type { ConfigEditScreenDefinition } from "../../types";
@@ -195,3 +196,72 @@ describe("validateI18nSurfaceKeys — gated configEdit section description (PR #
195
196
  expect(withoutDescription.description).toBeUndefined();
196
197
  });
197
198
  });
199
+
200
+ // fw#2313: a hand-written dot-form label is indistinguishable from literal
201
+ // display text, so isI18nKey only requires a translation for one explicitly
202
+ // marked via i18nKey() — unmarked dot-form labels must keep failing silently
203
+ // (that's the existing, intentional behavior for literal text like
204
+ // "actions.open"), never crashing boot on their own.
205
+ describe("validateI18nSurfaceKeys — explicit i18nKey() dot-form opt-in (fw#2313)", () => {
206
+ test("i18nKey()-marked dot-form label without a translation fails boot naming the key", () => {
207
+ const feature = defineFeature("fw2313boot", (r) => {
208
+ r.nav({ id: "home", label: i18nKey("fw2313.boot.optin.nav") });
209
+ });
210
+ expect(() => validateBoot([feature])).toThrow(
211
+ /required translation key missing: "fw2313\.boot\.optin\.nav"/,
212
+ );
213
+ });
214
+
215
+ test("i18nKey()-marked dot-form label with a translation passes boot", () => {
216
+ const feature = defineFeature("fw2313boot", (r) => {
217
+ r.nav({ id: "home", label: i18nKey("fw2313.boot.optin.nav.translated") });
218
+ r.translations({ keys: { "fw2313.boot.optin.nav.translated": { en: "Home" } } });
219
+ });
220
+ expect(() => validateBoot([feature])).not.toThrow();
221
+ });
222
+
223
+ test("unmarked literal dot-form label never fails boot", () => {
224
+ const feature = defineFeature("fw2313boot", (r) => {
225
+ r.nav({ id: "home", label: "fw2313.boot.never-marked.literal" });
226
+ });
227
+ expect(() => validateBoot([feature])).not.toThrow();
228
+ });
229
+ });
230
+
231
+ // The mask.title bypass is unconditional (required-surface-keys.ts,
232
+ // requiredKeysFromFeature) — mask.title is always an i18n key by
233
+ // ConfigKeyDefinition contract, never literal display text, so it needs no
234
+ // i18nKey() marker.
235
+ describe("validateI18nSurfaceKeys — mask.title is unconditionally required (fw#2313)", () => {
236
+ function maskedFeature(translationKeys: Record<string, { readonly en: string }>) {
237
+ return defineFeature("fw2313mask", (r) => {
238
+ r.config({
239
+ keys: {
240
+ apiKey: createTenantConfig("text", {
241
+ write: access.roles("TenantAdmin"),
242
+ mask: { title: "fw2313.boot.mask.title" },
243
+ }),
244
+ },
245
+ });
246
+ if (Object.keys(translationKeys).length > 0) {
247
+ r.translations({ keys: translationKeys });
248
+ }
249
+ });
250
+ }
251
+
252
+ test("masked config key without a mask.title translation fails boot", () => {
253
+ const feature = maskedFeature({});
254
+ expect(() => validateBoot([configHub, feature])).toThrow(
255
+ /required translation key missing: "fw2313\.boot\.mask\.title"/,
256
+ );
257
+ });
258
+
259
+ test("masked config key with a mask.title translation passes boot", () => {
260
+ const feature = maskedFeature({
261
+ "fw2313.boot.mask.title": { en: "API Key" },
262
+ "screen:fw2313mask-tenant.title": { en: "Settings" },
263
+ "fw2313mask.settings": { en: "Settings" },
264
+ });
265
+ expect(() => validateBoot([configHub, feature])).not.toThrow();
266
+ });
267
+ });
@@ -0,0 +1,12 @@
1
+ // Dot-form labels are indistinguishable from literal display text ("actions.open"),
2
+ // so authors mark the i18n ones explicitly; the registry is what boot validation reads.
3
+ const explicitDotFormKeys = new Set<string>();
4
+
5
+ export function i18nKey(value: string): string {
6
+ explicitDotFormKeys.add(value);
7
+ return value;
8
+ }
9
+
10
+ export function isExplicitDotFormKey(value: string): boolean {
11
+ return explicitDotFormKeys.has(value);
12
+ }
@@ -205,6 +205,7 @@ export {
205
205
  filterReadFields,
206
206
  } from "./field-access";
207
207
  export { resolveName, withResponseData } from "./handler-helpers";
208
+ export { i18nKey } from "./i18n-key";
208
209
  // findForbiddenMembershipRole/isForbiddenMembershipRole/
209
210
  // stripForbiddenMembershipRoles/buildSessionRoles are Public API for host
210
211
  // apps that build their own membership handlers. FORBIDDEN_MEMBERSHIP_ROLES
@@ -1,3 +1,4 @@
1
+ import { isExplicitDotFormKey } from "../engine/i18n-key";
1
2
  import {
2
3
  isExtensionEditSection,
3
4
  isWriteFormEditSection,
@@ -63,13 +64,14 @@ export function screenTitleKey(screenId: string): string {
63
64
  }
64
65
 
65
66
  function isI18nKey(value: string): boolean {
66
- return value.includes(":");
67
+ return value.includes(":") || isExplicitDotFormKey(value);
67
68
  }
68
69
 
69
70
  /**
70
71
  * `treatAsKey` bypasses the colon-only `isI18nKey` check — used by the Settings-Hub
71
72
  * generator's dot-form labels (`${feature}.settings`, mask titles), which are
72
73
  * always i18n references by construction, never literal display text (fw#2260).
74
+ * `isI18nKey` itself also accepts dot-form labels explicitly marked via `i18nKey()` (fw#2313).
73
75
  */
74
76
  function pushKey(out: Set<string>, value: string | undefined, treatAsKey = false): void {
75
77
  if (value !== undefined && (treatAsKey || isI18nKey(value))) out.add(value);
@@ -348,7 +350,8 @@ export function requiredKeysFromFeature(feature: FeatureDefinition): readonly st
348
350
  for (const key of requiredKeysFromWorkspace(ws)) out.add(key);
349
351
  }
350
352
  for (const def of Object.values(feature.configKeys)) {
351
- pushKey(out, def.mask?.title);
353
+ // mask.title is always an i18n key by construction (ConfigKeyDefinition contract), not literal text.
354
+ pushKey(out, def.mask?.title, true);
352
355
  }
353
356
  collectEntityListFilterKeys(feature, out);
354
357