@cosmicdrift/kumiko-renderer 0.362.0 → 0.363.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.
@@ -24,5 +24,8 @@ export function ContentPreview({ content, variables, contentFormat, }) {
24
24
  const safeVariables = contentFormat === "rich"
25
25
  ? Object.fromEntries(Object.entries(variables).map(([name, value]) => [name, escapeHtmlAttr(value)]))
26
26
  : variables;
27
- return (_jsx(Editor, { id: id, value: substituteVariables(content, safeVariables), onChange: noop, variables: [], readOnly: true }));
27
+ // "document" keeps `{{x}}` as-is: substituting it into a button/link href would make the
28
+ // schema drop that node; variable chips stay visible.
29
+ const value = contentFormat === "document" ? content : substituteVariables(content, safeVariables);
30
+ return _jsx(Editor, { id: id, value: value, onChange: noop, variables: [], readOnly: true });
28
31
  }
@@ -1,5 +1,5 @@
1
1
  import type { ScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
- import type { ListRowViewModel, Translate } from "@cosmicdrift/kumiko-headless";
2
+ import type { FormValues, ListRowViewModel, Translate } from "@cosmicdrift/kumiko-headless";
3
3
  import { type ReactNode } from "react";
4
4
  import { type ListSort } from "../hooks/use-list-url-state.js";
5
5
  import type { FeatureSchema } from "./feature-schema.js";
@@ -19,7 +19,7 @@ export { qualifyScreenId };
19
19
  export declare function qualifyNavId(featureName: string, navId: string): string;
20
20
  export { screenAccessAllows };
21
21
  export declare function KumikoScreen({ schema, qn, translate, entityId, onRowClick, onCopyLink, }: KumikoScreenProps): ReactNode;
22
- export declare function buildInitialValues(fields: Readonly<Record<string, unknown>>, defaultCurrency?: string, moneyCurrencyOverrides?: Readonly<Record<string, string>>): Readonly<Record<string, unknown>>;
22
+ export declare function buildInitialValues(fields: Readonly<Record<string, unknown>>, defaultCurrency?: string, moneyCurrencyOverrides?: Readonly<Record<string, string>>): FormValues;
23
23
  export declare function tenantCurrencyMoneyFieldNames(fields: Readonly<Record<string, unknown>>): readonly string[];
24
24
  export declare function literalCurrencyOverrides(fields: Readonly<Record<string, unknown>>): Readonly<Record<string, string>>;
25
25
  export declare function useMoneyCurrencyOverrides(fields: Readonly<Record<string, unknown>>, tenantFieldNames: readonly string[], fallback: string | undefined): {
@@ -4,6 +4,7 @@ import { applyFormatSpec, buildOptionLabels, computeRelatedListSectionViewModel,
4
4
  import { resolveActionIcon } from "@cosmicdrift/kumiko-types/action-icon";
5
5
  import { TENANT_CURRENCY_CONFIG_KEY } from "@cosmicdrift/kumiko-types/fields";
6
6
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
7
+ import { isPlainObject, propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
7
8
  import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
8
9
  import { QueryOptionLabel, resolveOptionsQueryPayload, } from "../components/query-options-select.js";
9
10
  import { extractCreatedId, extractIdField } from "../components/reference-create-dialog.js";
@@ -246,24 +247,29 @@ function useNavigateToCreateFor(schema, entityName, createScreen, createUnavaila
246
247
  // — used for a `money` field that declares `currency: { kind: "tenant" }`, so
247
248
  // its empty initial value carries the tenant's own currency instead of the
248
249
  // entity-wide default.
250
+ function versionOf(record) {
251
+ const version = record["version"];
252
+ return typeof version === "number" ? version : 1;
253
+ }
249
254
  export function buildInitialValues(fields, defaultCurrency, moneyCurrencyOverrides) {
250
255
  const out = {};
251
256
  for (const [name, def] of Object.entries(fields)) {
252
- const shape = def;
253
- if (shape.default !== undefined) {
254
- out[name] = shape.default;
257
+ const type = propertyOf(def, "type");
258
+ const defaultValue = propertyOf(def, "default");
259
+ if (defaultValue !== undefined) {
260
+ out[name] = defaultValue;
255
261
  continue;
256
262
  }
257
263
  // An untouched optional number must not persist as a deliberate 0.
258
- if (shape.type === "number" && shape.required !== true) {
264
+ if (type === "number" && propertyOf(def, "required") !== true) {
259
265
  out[name] = undefined;
260
266
  continue;
261
267
  }
262
- if (shape.type === "embedded" && shape.multiple === true) {
268
+ if (type === "embedded" && propertyOf(def, "multiple") === true) {
263
269
  out[name] = [];
264
270
  continue;
265
271
  }
266
- if (shape.type === "money") {
272
+ if (type === "money") {
267
273
  const currency = moneyCurrencyOverrides?.[name] ?? defaultCurrency;
268
274
  if (currency !== undefined) {
269
275
  out[name] = { amount: 0, currency };
@@ -271,11 +277,11 @@ export function buildInitialValues(fields, defaultCurrency, moneyCurrencyOverrid
271
277
  }
272
278
  }
273
279
  out[name] =
274
- shape.type === "boolean"
280
+ type === "boolean"
275
281
  ? false
276
- : shape.type === "number" || shape.type === "money"
282
+ : type === "number" || type === "money"
277
283
  ? 0
278
- : shape.type === "multiSelect"
284
+ : type === "multiSelect"
279
285
  ? []
280
286
  : "";
281
287
  }
@@ -290,9 +296,10 @@ export function buildInitialValues(fields, defaultCurrency, moneyCurrencyOverrid
290
296
  export function tenantCurrencyMoneyFieldNames(fields) {
291
297
  const names = [];
292
298
  for (const [name, def] of Object.entries(fields)) {
293
- const shape = def;
294
- if (shape.type === "money" && shape.currency?.kind === "tenant")
299
+ if (propertyOf(def, "type") === "money" &&
300
+ propertyOf(propertyOf(def, "currency"), "kind") === "tenant") {
295
301
  names.push(name);
302
+ }
296
303
  }
297
304
  return names;
298
305
  }
@@ -327,9 +334,11 @@ const UPDATE_CHANGES_KEY = "changes";
327
334
  export function literalCurrencyOverrides(fields) {
328
335
  const out = {};
329
336
  for (const [name, def] of Object.entries(fields)) {
330
- const shape = def;
331
- const code = shape.currency?.code;
332
- if (shape.type === "money" && shape.currency?.kind === "literal" && code !== undefined) {
337
+ const currency = propertyOf(def, "currency");
338
+ const code = propertyOf(currency, "code");
339
+ if (propertyOf(def, "type") === "money" &&
340
+ propertyOf(currency, "kind") === "literal" &&
341
+ typeof code === "string") {
333
342
  out[name] = code;
334
343
  }
335
344
  }
@@ -375,7 +384,8 @@ const CURRENCY_CODE_PATTERN = /^[A-Za-z]{3}$/;
375
384
  function isMoneyValue(value) {
376
385
  if (typeof value !== "object" || value === null)
377
386
  return false;
378
- const { amount, currency } = value;
387
+ const amount = propertyOf(value, "amount");
388
+ const currency = propertyOf(value, "currency");
379
389
  return (typeof amount === "number" &&
380
390
  Number.isFinite(amount) &&
381
391
  typeof currency === "string" &&
@@ -526,7 +536,7 @@ function coerceEmbeddedListRows(raw, fieldName, schema, maxItems) {
526
536
  }
527
537
  const cellEntries = [];
528
538
  for (const [cellName, cell] of Object.entries(schema)) {
529
- const cellValue = candidate[cellName];
539
+ const cellValue = propertyOf(candidate, cellName);
530
540
  if (cellValue === undefined || cellValue === null)
531
541
  continue;
532
542
  const coerced = coerceEmbeddedCell(cellValue, cell);
@@ -762,7 +772,7 @@ function EntityEditUpdateBody({ schema, screen, entity, entityId, translate, onC
762
772
  // der ursprünglichen version sitzen — der buildPayload würde
763
773
  // weiter die stale version stampen und der Konflikt-Recovery wäre
764
774
  // kaputt.
765
- const recordKey = record.version ?? 1;
775
+ const recordKey = versionOf(record);
766
776
  return (_jsx(EntityEditUpdateForm, { schema: schema, screen: screen, entity: entity, entityId: entityId, record: record, onReload: detailQuery.refetch, ...(translate !== undefined && { translate }), ...(onCopyLink !== undefined && { onCopyLink }), ...(autoReloadOnSave === true && { onSaved: () => void detailQuery.refetch() }), ...(onDeleted !== undefined && { onDeleted }) }, `${entityId}:${recordKey}`));
767
777
  }
768
778
  function EntityEditUpdateForm({ schema, screen, entity, entityId, record, onReload, translate, onCopyLink, onSaved, onDeleted, }) {
@@ -773,7 +783,7 @@ function EntityEditUpdateForm({ schema, screen, entity, entityId, record, onRelo
773
783
  // captured once and stamped into every update payload — if a
774
784
  // concurrent writer bumps it, the server returns a version-conflict
775
785
  // error and the user reloads.
776
- const recordVersion = record.version ?? 1;
786
+ const recordVersion = versionOf(record);
777
787
  const entityDefaultCurrency = entity.defaultCurrency ?? "EUR";
778
788
  // A money field declaring `currency: { kind: "tenant" }` (fw#2933) resolves
779
789
  // its empty-value currency from the tenant-settings config key instead of
@@ -800,9 +810,7 @@ function EntityEditUpdateForm({ schema, screen, entity, entityId, record, onRelo
800
810
  // damit sie beim Edit den Bestand zeigen statt write-only zu sein.
801
811
  const extensionInitialValues = useMemo(() => {
802
812
  const cf = record["customFields"];
803
- return cf !== null && typeof cf === "object" && !Array.isArray(cf)
804
- ? cf
805
- : undefined;
813
+ return isPlainObject(cf) ? cf : undefined;
806
814
  }, [record]);
807
815
  const writeCommand = entityWriteCommand(schema.featureName, screen.entity, "update");
808
816
  const deleteCommand = entityWriteCommand(schema.featureName, screen.entity, "delete");
@@ -1217,8 +1225,8 @@ function EntityListBody({ schema, screen, entity, translate, onRowClick, }) {
1217
1225
  const filterPayload = useMemo(() => buildFilterPayload(urlState.filters, (field) => {
1218
1226
  // entity.fields ist am Renderer-Layer schwach getypt (vom Schema
1219
1227
  // deserialisiert) — Boundary-Cast wie buildInitialValues.
1220
- const def = entity.fields[field];
1221
- return def?.type;
1228
+ const type = propertyOf(entity.fields[field], "type");
1229
+ return typeof type === "string" ? type : undefined;
1222
1230
  }), [urlState.filters, entity.fields]);
1223
1231
  // Entity-only additions (screen.filter, faceted filters) layer on top of
1224
1232
  // the shared buildListQueryPayload — projectionList has neither.
@@ -2379,7 +2387,7 @@ function navigateAfterRedirect(options) {
2379
2387
  function overlayUpdatedProjection(record, resultData) {
2380
2388
  if (typeof resultData !== "object" || resultData === null)
2381
2389
  return record;
2382
- const projection = resultData.data;
2390
+ const projection = propertyOf(resultData, "data");
2383
2391
  if (typeof projection !== "object" || projection === null)
2384
2392
  return record;
2385
2393
  return { ...record, ...projection };
@@ -2628,7 +2636,7 @@ function ConfigEditBody({ schema, screen, translate, }) {
2628
2636
  out[shortName] = defaults[shortName];
2629
2637
  continue;
2630
2638
  }
2631
- const ftype = fieldDef.type;
2639
+ const ftype = propertyOf(fieldDef, "type");
2632
2640
  // Field-Type-Coercion: config-Werte sind string|number|boolean,
2633
2641
  // aber der Form-State erwartet das passende Field-Native-Type.
2634
2642
  if (ftype === "number" || ftype === "money") {
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
2
3
  import { useCallback, useMemo, useRef, useState } from "react";
3
4
  import { RenderEdit } from "../components/render-edit.js";
4
5
  import { useTranslation } from "../i18n.js";
@@ -14,7 +15,7 @@ function extractRevealValue(data, field) {
14
15
  return undefined;
15
16
  if (!Object.hasOwn(data, field))
16
17
  return undefined;
17
- return data[field];
18
+ return propertyOf(data, field);
18
19
  }
19
20
  function extractCarriedValues(data, carry) {
20
21
  const carried = {};
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
2
3
  import { useMemo } from "react";
3
4
  import { buildInitialValues, tenantCurrencyMoneyFieldNames, useMoneyCurrencyOverrides, } from "../app/kumiko-screen.js";
4
5
  import { toKebab } from "../app/qn.js";
@@ -24,7 +25,7 @@ export function extractIdField(data, field) {
24
25
  return undefined;
25
26
  if (!Object.hasOwn(data, field))
26
27
  return undefined;
27
- const value = data[field];
28
+ const value = propertyOf(data, field);
28
29
  return typeof value === "string" ? value : undefined;
29
30
  }
30
31
  export function extractCreatedId(data) {
@@ -1,6 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { evalFieldCondition, isFieldsEditSection, normalizeEditField, PROJECTION_DETAIL_ENTITY, sectionFieldSpecs, } from "@cosmicdrift/kumiko-framework/ui-types";
3
3
  import { computeEditViewModel, groupIssuesByPath, relevantFieldIssues, zodErrorToFieldIssues, } from "@cosmicdrift/kumiko-headless";
4
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
4
5
  import { RenderEditActionButton } from "./render-edit-action-button.js";
5
6
  import { AllFieldsRequiredProvider } from "./render-field.js";
6
7
  import { useActionMenuItems } from "./use-action-menu-items.js";
@@ -49,9 +50,13 @@ function newDraftPrefix(screenId) {
49
50
  // `crypto.randomUUID` is missing in non-secure contexts and React Native/Hermes
50
51
  // without a polyfill — guard like dispatcher-live.ts's generateRequestId.
51
52
  function mintDraftId() {
52
- const c = globalThis.crypto;
53
- if (typeof c?.randomUUID === "function")
54
- return c.randomUUID();
53
+ const webCrypto = propertyOf(globalThis, "crypto");
54
+ const randomUUID = propertyOf(webCrypto, "randomUUID");
55
+ if (typeof randomUUID === "function") {
56
+ const id = Reflect.apply(randomUUID, webCrypto, []);
57
+ if (typeof id === "string")
58
+ return id;
59
+ }
55
60
  return `draft-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
56
61
  }
57
62
  // End-to-end renderer für einen entityEdit screen. Rendert aus-
@@ -551,7 +556,7 @@ export function RenderEdit(props) {
551
556
  // was in flight — their input wins over the stored draft.
552
557
  if (controller.getSnapshot().isDirty || hasNavigatedRef.current)
553
558
  return;
554
- // @cast-boundary form-draft blob: `values` round-trips through an opaque
559
+ // @cast-boundary form-values draft blob: `values` round-trips through an opaque
555
560
  // jsonb column and comes back untyped.
556
561
  controller.setValues(draft.values);
557
562
  setRawStep(Math.max(draft.stepIndex, 0));
@@ -1,6 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { isFormatSpec, SYSTEM_REFERENCE_LABELS, } from "@cosmicdrift/kumiko-framework/ui-types";
3
3
  import { applyFormatSpec, currencyDecimals, } from "@cosmicdrift/kumiko-headless";
4
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
4
5
  import { createContext, useCallback, useContext, useMemo, useState } from "react";
5
6
  import { useAppFeatures } from "../app/app-features-context.js";
6
7
  import { useColumnRenderer } from "../app/column-renderers.js";
@@ -159,7 +160,7 @@ function ReferenceInput({ field, id, hasError, onChange, Input, featureName, })
159
160
  }
160
161
  setCreatedWithoutIdWarning(false);
161
162
  if (isMultiple) {
162
- const current = Array.isArray(field.value) ? field.value : [];
163
+ const current = stringArrayOf(field.value);
163
164
  onChange([...current, newId]);
164
165
  }
165
166
  else {
@@ -194,9 +195,7 @@ function ReferenceInput({ field, id, hasError, onChange, Input, featureName, })
194
195
  };
195
196
  const createDialog = (_jsxs(_Fragment, { children: [canCreate && refCreateScreen && refEntityDef && (_jsx(ReferenceCreateDialog, { open: createOpen, onClose: () => setCreateOpen(false), onCreated: handleCreated, featureName: refFeature, screen: refCreateScreen, entity: refEntityDef })), createdWithoutIdWarning && (_jsx(Banner, { variant: "error", children: t("kumiko.field.reference-created-no-id") }))] }));
196
197
  if (isMultiple) {
197
- const arrayValue = Array.isArray(field.value)
198
- ? field.value
199
- : [];
198
+ const arrayValue = stringArrayOf(field.value);
200
199
  return (_jsxs(_Fragment, { children: [_jsx(Input, { kind: "combobox", ...baseInputProps, multiple: true, value: arrayValue, onChange: (v) => onChange(v) }), createDialog] }));
201
200
  }
202
201
  const stringValue = field.value === undefined || field.value === null ? "" : String(field.value);
@@ -228,7 +227,7 @@ function FieldRendererOutput({ field, renderer, row, appLocale, }) {
228
227
  // when set; coalesce undefined (spread override) back to appLocale (#2332).
229
228
  return (_jsx(Text, { testId: `field-value-${field.field}`, children: applyFormatSpec({
230
229
  ...renderer,
231
- locale: renderer.locale ?? appLocale,
230
+ locale: stringPropertyOf(renderer, "locale") ?? appLocale,
232
231
  }, field.value, t) }));
233
232
  }
234
233
  if (componentName !== undefined) {
@@ -254,9 +253,7 @@ function ReadOnlyReferenceValue({ field, featureName, }) {
254
253
  limit: REFERENCE_COMBOBOX_LIMIT,
255
254
  });
256
255
  const ids = isMultiple
257
- ? Array.isArray(field.value)
258
- ? field.value
259
- : []
256
+ ? stringArrayOf(field.value)
260
257
  : typeof field.value === "string" && field.value !== ""
261
258
  ? [field.value]
262
259
  : [];
@@ -354,7 +351,7 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
354
351
  case "tz":
355
352
  return (_jsx(Input, { kind: "tz", ...common, value: stringValue(field.value), onChange: (v) => onChange(v) }));
356
353
  case "multiSelect": {
357
- const arrayValue = Array.isArray(field.value) ? field.value : [];
354
+ const arrayValue = stringArrayOf(field.value);
358
355
  const renderMultiSelect = (options) => field.display === "checkboxes" ? (_jsx(MultiSelectCheckboxes, { field: field, id: id, options: options, value: arrayValue, onChange: (v) => onChange(v) })) : (_jsx(Input, { kind: "combobox", ...common, multiple: true, value: arrayValue, onChange: (v) => onChange(v), options: options }));
359
356
  if (field.selectOptionsAvailabilityQuery !== undefined && field.readOnly !== true) {
360
357
  return (_jsx(AvailabilityOptionsMultiSelect, { field: field, query: field.selectOptionsAvailabilityQuery.query, currentValues: arrayValue, children: renderMultiSelect }));
@@ -474,13 +471,20 @@ function resolveNumberUnit(unit, row) {
474
471
  const trimmed = sibling.trim();
475
472
  return trimmed.length > 0 && trimmed.length <= MAX_SIBLING_UNIT_LENGTH ? trimmed : undefined;
476
473
  }
474
+ function stringArrayOf(value) {
475
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : [];
476
+ }
477
+ function stringPropertyOf(value, key) {
478
+ const property = propertyOf(value, key);
479
+ return typeof property === "string" ? property : undefined;
480
+ }
477
481
  function moneyMinorValue(v, currency) {
478
482
  if (v === undefined || v === null || v === "")
479
483
  return "";
480
484
  if (typeof v === "number")
481
485
  return Math.round(v * 10 ** currencyDecimals(currency));
482
486
  if (typeof v === "object") {
483
- const amount = v.amount;
487
+ const amount = propertyOf(v, "amount");
484
488
  if (typeof amount === "number")
485
489
  return Math.round(amount * 10 ** currencyDecimals(currency));
486
490
  }
@@ -498,11 +502,11 @@ function moneyPayload(minorUnits, currency) {
498
502
  // (noch nicht gesetzt) → "" als Empty-Sentinel, analog money/timestamp.
499
503
  function locatedValue(v) {
500
504
  if (v !== null && typeof v === "object" && "at" in v && "tz" in v) {
501
- const o = v;
505
+ const utc = propertyOf(v, "utc");
502
506
  return {
503
- at: typeof o.at === "string" ? o.at : "",
504
- tz: typeof o.tz === "string" ? o.tz : "",
505
- ...(typeof o.utc === "string" && { utc: o.utc }),
507
+ at: typeof v.at === "string" ? v.at : "",
508
+ tz: typeof v.tz === "string" ? v.tz : "",
509
+ ...(typeof utc === "string" && { utc }),
506
510
  };
507
511
  }
508
512
  return "";
@@ -14,11 +14,10 @@ function buildWriteFormSchema(fields) {
14
14
  .object({})
15
15
  .passthrough()
16
16
  .superRefine((values, ctx) => {
17
- const record = values;
18
17
  for (const field of fields) {
19
18
  if (field.readOnly || !field.visible || !field.required)
20
19
  continue;
21
- if (isPresent(record[field.field]))
20
+ if (isPresent(values[field.field]))
22
21
  continue;
23
22
  ctx.addIssue({
24
23
  code: "custom",
@@ -261,6 +261,7 @@ export const kumikoDefaultTranslations = {
261
261
  "errors.internal": "Something went wrong. Please try again later.",
262
262
  "errors.rate_limited": "Too many requests. Please try again shortly.",
263
263
  "errors.rate_limit_unavailable": "Rate limiting is temporarily unavailable. Please try again shortly.",
264
+ "errors.puppeteer_unavailable": "The PDF service is not reachable. PDFs cannot be created right now.",
264
265
  "errors.cap.exceeded": "Limit reached. Upgrade your plan or wait for the next period.",
265
266
  "errors.download.urlMissing": "Download unavailable — please try again.",
266
267
  "auth.errors.originNotAllowed": "Requests from this origin are not allowed.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer",
3
- "version": "0.362.0",
3
+ "version": "0.363.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,9 +15,9 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@cosmicdrift/kumiko-framework": "0.362.0",
19
- "@cosmicdrift/kumiko-headless": "0.362.0",
20
- "@cosmicdrift/kumiko-types": "0.362.0",
18
+ "@cosmicdrift/kumiko-framework": "0.363.0",
19
+ "@cosmicdrift/kumiko-headless": "0.363.0",
20
+ "@cosmicdrift/kumiko-types": "0.363.0",
21
21
  "react": "^19.2.6",
22
22
  "temporal-polyfill": "^0.3.2",
23
23
  "zod": "^4.4.3"
@@ -28,7 +28,7 @@
28
28
  "@types/react-dom": "^19.2.3",
29
29
  "jsdom": "^29.1.1",
30
30
  "react-dom": "^19.2.6",
31
- "@cosmicdrift/kumiko-locale-de": "0.362.0"
31
+ "@cosmicdrift/kumiko-locale-de": "0.363.0"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
package/src/changes.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.363.0",
4
+ "type": "improvement",
5
+ "title": "propertyOf type guard and fewer unchecked casts in the UI packages",
6
+ "detail": "\"@cosmicdrift/kumiko-types/type-guards\" exports propertyOf, which reads one property off an unknown value. Headless, renderer, renderer-web and dispatcher-live replace unchecked as-casts and globalThis probes with it and with narrowing. Behavior stays the same for well-formed input; a format spec, upload response or stream event with a wrongly typed property is now ignored instead of passed through."
7
+ },
2
8
  {
3
9
  "version": "0.362.0",
4
10
  "type": "improvement",