@cosmicdrift/kumiko-renderer 0.365.1 → 0.367.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.
@@ -23,7 +23,7 @@
23
23
  // // feature.ts (server-only — imports defineFeature)
24
24
  // import { taskEntity, editScreen, ... } from "./feature-schema.js";
25
25
  // export const taskFeature = defineFeature("tasks", (r) => {
26
- // r.entity("task", taskEntity);
26
+ // r.entity({ name: "task", ...taskEntity });
27
27
  // r.writeHandler(...);
28
28
  // r.screen(editScreen);
29
29
  // ...
@@ -19,6 +19,13 @@ 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
+ /** Empty state for a screen QN that is not in the schema (feature disabled for
23
+ * the tenant, stale link, miswired screen). The QN only goes to the console. */
24
+ export declare function ScreenMissingState({ screenQn, homeScreenId, testId, }: {
25
+ readonly screenQn: string;
26
+ readonly homeScreenId?: string;
27
+ readonly testId?: string;
28
+ }): ReactNode;
22
29
  export declare function buildInitialValues(fields: Readonly<Record<string, unknown>>, defaultCurrency?: string, moneyCurrencyOverrides?: Readonly<Record<string, string>>): FormValues;
23
30
  export declare function tenantCurrencyMoneyFieldNames(fields: Readonly<Record<string, unknown>>): readonly string[];
24
31
  export declare function literalCurrencyOverrides(fields: Readonly<Record<string, unknown>>): Readonly<Record<string, string>>;
@@ -61,7 +61,6 @@ export function qualifyNavId(featureName, navId) {
61
61
  }
62
62
  export { screenAccessAllows };
63
63
  export function KumikoScreen({ schema, qn, translate, entityId, onRowClick, onCopyLink, }) {
64
- const { Banner, Text } = usePrimitives();
65
64
  const userRoles = useUserRoles();
66
65
  const outerHost = useReturnHost();
67
66
  const screen = useMemo(() => schema.screens.find((s) => qualifyScreenId(schema.featureName, s.id) === qn), [schema.featureName, schema.screens, qn]);
@@ -71,10 +70,10 @@ export function KumikoScreen({ schema, qn, translate, entityId, onRowClick, onCo
71
70
  ...(entityId !== undefined && { entityId }),
72
71
  }), [screen, entityId]);
73
72
  if (!screen) {
74
- return (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-screen-not-found", children: ["Screen not found: ", _jsx(Text, { variant: "code", children: qn })] }));
73
+ return _jsx(ScreenMissingState, { screenQn: qn });
75
74
  }
76
75
  if (!screenAccessAllows(screen.access, userRoles)) {
77
- return (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-screen-access-denied", children: ["Access denied: ", _jsx(Text, { variant: "code", children: qn })] }));
76
+ return _jsx(ScreenAccessDeniedBanner, { screenQn: qn, testId: "kumiko-screen-access-denied" });
78
77
  }
79
78
  const screenBody = renderScreenBody({
80
79
  schema,
@@ -112,6 +111,30 @@ function ScreenVisibilityGate({ featureName, visibleWhen, fallback, translate, c
112
111
  const hideLockedBreadcrumb = PageHeader !== undefined && pageHeaderSlotAvailable ? _jsx(PageHeader, { hideBreadcrumb: true }) : null;
113
112
  return (_jsxs(_Fragment, { children: [hideLockedBreadcrumb, fallback !== undefined ? (_jsx(FallbackScreen, { featureName: featureName, fallback: fallback, translate: translate })) : (_jsx(ScreenUnavailableBanner, {}))] }));
114
113
  }
114
+ function ScreenMissingHomeButton({ homeScreenId, testId, }) {
115
+ const { Button } = usePrimitives();
116
+ const t = useTranslation();
117
+ const nav = useNav();
118
+ return (_jsx(Button, { variant: "secondary", onClick: () => nav.navigate({ screenId: homeScreenId }), testId: testId, children: t("kumiko.screen-missing.home") }));
119
+ }
120
+ /** Empty state for a screen QN that is not in the schema (feature disabled for
121
+ * the tenant, stale link, miswired screen). The QN only goes to the console. */
122
+ export function ScreenMissingState({ screenQn, homeScreenId, testId = "kumiko-screen-not-found", }) {
123
+ const { Card, Text } = usePrimitives();
124
+ const t = useTranslation();
125
+ useEffect(() => {
126
+ console.warn(`[kumiko] screen "${screenQn}" is not in the schema (feature disabled, stale link or miswired screen).`);
127
+ }, [screenQn]);
128
+ return (_jsxs(Card, { options: { padded: true }, testId: testId, children: [_jsx(Text, { testId: `${testId}-title`, children: t("kumiko.screen-missing.title") }), _jsx(Text, { variant: "muted", children: t("kumiko.screen-missing.hint") }), homeScreenId !== undefined && (_jsx(ScreenMissingHomeButton, { homeScreenId: homeScreenId, testId: `${testId}-home` }))] }));
129
+ }
130
+ function ScreenAccessDeniedBanner({ screenQn, testId, }) {
131
+ const { Banner } = usePrimitives();
132
+ const t = useTranslation();
133
+ useEffect(() => {
134
+ console.warn(`[kumiko] access to screen "${screenQn}" denied for the current roles.`);
135
+ }, [screenQn]);
136
+ return (_jsx(Banner, { padded: true, variant: "error", testId: testId, children: t("kumiko.screen-access-denied.title") }));
137
+ }
115
138
  function ScreenUnavailableBanner() {
116
139
  const { Banner } = usePrimitives();
117
140
  const t = useTranslation();
@@ -1143,7 +1166,7 @@ function useDrawerAction(schema) {
1143
1166
  };
1144
1167
  }
1145
1168
  function DrawerHost({ schema, drawerAction, drawerScreen, drawerInitialValues, userRoles, translate, onClose, onSuccess, }) {
1146
- const { Drawer, Banner, Text, Dialog } = usePrimitives();
1169
+ const { Drawer, Dialog } = usePrimitives();
1147
1170
  const t = useTranslation();
1148
1171
  const effectiveTranslate = translate ?? t;
1149
1172
  const locale = useLocale().locale();
@@ -1185,7 +1208,7 @@ function DrawerHost({ schema, drawerAction, drawerScreen, drawerInitialValues, u
1185
1208
  requestClose();
1186
1209
  }, title: effectiveTranslate(drawerAction.title ?? drawerAction.label, titleParams), ...(drawerAction.subtitle !== undefined && {
1187
1210
  subtitle: effectiveTranslate(drawerAction.subtitle, titleParams),
1188
- }), testId: `toolbar-drawer-${drawerAction.id}`, children: _jsx(PageHeaderSlotAvailableProvider, { value: false, children: _jsx(InsideDrawerProvider, { value: true, children: drawerScreen === undefined ? (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-toolbar-drawer-not-found", children: ["Screen not found: ", _jsx(Text, { variant: "code", children: drawerAction.screen })] })) : !allowed ? (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-toolbar-drawer-access-denied", children: ["Access denied: ", _jsx(Text, { variant: "code", children: drawerAction.screen })] })) : (_jsx(ActionFormBody, { schema: schema, screen: drawerScreen, ...(translate !== undefined && { translate }), ...(drawerInitialValues !== undefined && {
1211
+ }), testId: `toolbar-drawer-${drawerAction.id}`, children: _jsx(PageHeaderSlotAvailableProvider, { value: false, children: _jsx(InsideDrawerProvider, { value: true, children: drawerScreen === undefined ? (_jsx(ScreenMissingState, { screenQn: drawerAction.screen, testId: "kumiko-toolbar-drawer-not-found" })) : !allowed ? (_jsx(ScreenAccessDeniedBanner, { screenQn: drawerAction.screen, testId: "kumiko-toolbar-drawer-access-denied" })) : (_jsx(ActionFormBody, { schema: schema, screen: drawerScreen, ...(translate !== undefined && { translate }), ...(drawerInitialValues !== undefined && {
1189
1212
  initialOverrides: drawerInitialValues,
1190
1213
  }), onSuccess: () => {
1191
1214
  setHasUnsavedInput(false);
@@ -63,6 +63,7 @@ export function synthesizeProjectionDetailScreen(screen) {
63
63
  allowCreate: false,
64
64
  allowDelete: false,
65
65
  ...(screen.description !== undefined && { description: screen.description }),
66
+ ...(screen.recordTitleField !== undefined && { recordTitleField: screen.recordTitleField }),
66
67
  ...(screen.fieldLabels !== undefined && { fieldLabels: screen.fieldLabels }),
67
68
  ...(screen.slots !== undefined && { slots: screen.slots }),
68
69
  ...(screen.access !== undefined && { access: screen.access }),
@@ -0,0 +1,25 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from "react";
2
+ export type ErrorBoundaryFallbackProps = {
3
+ readonly error: Error;
4
+ readonly reset: () => void;
5
+ };
6
+ export type ErrorBoundaryProps = {
7
+ readonly children: ReactNode;
8
+ readonly fallback: (props: ErrorBoundaryFallbackProps) => ReactNode;
9
+ /** Prefix for the logged message, e.g. "screen" or "root". */
10
+ readonly scope: string;
11
+ /** Called after the default console.error log, e.g. to forward to telemetry. */
12
+ readonly onError?: (error: Error, info: ErrorInfo) => void;
13
+ };
14
+ type ErrorBoundaryState = {
15
+ readonly error: Error | undefined;
16
+ };
17
+ export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
18
+ state: ErrorBoundaryState;
19
+ static getDerivedStateFromError(thrown: unknown): ErrorBoundaryState;
20
+ componentDidCatch(thrown: unknown, info: ErrorInfo): void;
21
+ private logCaught;
22
+ private reset;
23
+ render(): ReactNode;
24
+ }
25
+ export {};
@@ -0,0 +1,35 @@
1
+ import { ERROR_BOUNDARY_LOG_MARKER } from "@cosmicdrift/kumiko-types/error-boundary";
2
+ import { Component } from "react";
3
+ function toError(thrown) {
4
+ return thrown instanceof Error ? thrown : new Error(String(thrown));
5
+ }
6
+ export class ErrorBoundary extends Component {
7
+ state = { error: undefined };
8
+ static getDerivedStateFromError(thrown) {
9
+ return { error: toError(thrown) };
10
+ }
11
+ componentDidCatch(thrown, info) {
12
+ const error = toError(thrown);
13
+ this.logCaught(error, info.componentStack);
14
+ try {
15
+ this.props.onError?.(error, info);
16
+ }
17
+ catch (handlerError) {
18
+ // A throw escaping componentDidCatch hands the error to the next boundary up, so a broken telemetry hook would replace the screen card with the root fallback.
19
+ this.logCaught(toError(handlerError), "onError handler threw");
20
+ }
21
+ }
22
+ logCaught(error, detail) {
23
+ // biome-ignore lint/suspicious/noConsole: the client has no logger and the boundary must not swallow the error; the e2e fixture detects boundary failures via this exact console.error line
24
+ console.error(`[kumiko] ${this.props.scope} ${ERROR_BOUNDARY_LOG_MARKER}:`, error, detail);
25
+ }
26
+ reset = () => {
27
+ this.setState({ error: undefined });
28
+ };
29
+ render() {
30
+ const { error } = this.state;
31
+ if (error === undefined)
32
+ return this.props.children;
33
+ return this.props.fallback({ error, reset: this.reset });
34
+ }
35
+ }
@@ -1240,6 +1240,12 @@ export function RenderEdit(props) {
1240
1240
  // floating as a heading above the band.
1241
1241
  const titleInSettingsHeader = isSettingsList && !titleInShell;
1242
1242
  const usesFieldFlow = isScreenForm || insideDrawer;
1243
+ // The caller's headerRegion already shows the record title in the shell page
1244
+ // header, so the type title ("Release") above the form would repeat it.
1245
+ const recordTitleInHeaderRegion = headerRegion !== undefined &&
1246
+ recordTitle !== undefined &&
1247
+ PageHeader !== undefined &&
1248
+ pageHeaderSlotAvailable;
1243
1249
  const allRequiredHint = (_jsx(Text, { variant: "muted", testId: "render-edit-all-required-hint", children: translate("kumiko.form.all-required") }));
1244
1250
  const stepRail = isWizard && isScreenForm && StepBar !== undefined;
1245
1251
  const upNextSection = screen.layout.wizard?.aside?.upNext === true ? filteredSections[currentStep + 1] : undefined;
@@ -1270,7 +1276,8 @@ export function RenderEdit(props) {
1270
1276
  actions: (_jsx(ActionOverflowMenu, { label: translate("kumiko.list.row-actions.more"), items: headerMenuItems, testId: "render-edit-header-menu" })),
1271
1277
  }) })), _jsx(Form, { onSubmit: () => void handleSubmit(), ...(hideSectionTitles !== true &&
1272
1278
  !titleInShell &&
1273
- !titleInSettingsHeader && { title: formTitle }), ...(formSubtitle !== undefined &&
1279
+ !titleInSettingsHeader &&
1280
+ !recordTitleInHeaderRegion && { title: formTitle }), ...(formSubtitle !== undefined &&
1274
1281
  !stepRail &&
1275
1282
  !titleInSettingsHeader && { subtitle: formSubtitle }), ...(isScreenForm && { screenForm: true }), ...(unsavedCount !== undefined &&
1276
1283
  (unsavedCount > 0 || hasDirtyFooter) && { unsavedCount }), ...(sectionNavItems.length > 0 && { sectionNav: sectionNavItems }), ...(stepRail && {
@@ -135,6 +135,13 @@ export const kumikoDefaultTranslations = {
135
135
  "kumiko.app-boot.no-open-screen": "This app has no screen that's accessible without a role restriction.",
136
136
  "kumiko.widget.loading": "Loading…",
137
137
  "kumiko.widget.error.title": "Couldn't load.",
138
+ "kumiko.screen-missing.title": "This page is not available",
139
+ "kumiko.screen-missing.hint": "It is not enabled for your account or no longer exists.",
140
+ "kumiko.screen-access-denied.title": "You do not have access to this page.",
141
+ "kumiko.screen-missing.home": "Go to start page",
142
+ "kumiko.screen-error.title": "This screen failed to render.",
143
+ "kumiko.screen-error.detail": "Screen: {screen}. {message}",
144
+ "kumiko.screen-error.screen": "Screen: {screen}",
138
145
  "kumiko.widget.upload.uploading": "Uploading…",
139
146
  "kumiko.widget.upload.done": "Uploaded",
140
147
  "kumiko.widget.upload.error": "Failed",
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@ export { ExtensionSectionsProvider, extensionSectionName, useExtensionSectionCom
19
19
  export type { AppSchema, FeatureSchema, QualifiedContentCollection, WorkspaceSchema, } from "./app/feature-schema.js";
20
20
  export { isAppSchema, toAppSchema } from "./app/feature-schema.js";
21
21
  export type { KumikoScreenProps } from "./app/kumiko-screen.js";
22
- export { KumikoScreen, qualifyNavId, qualifyScreenId } from "./app/kumiko-screen.js";
22
+ export { KumikoScreen, qualifyNavId, qualifyScreenId, ScreenMissingState, } from "./app/kumiko-screen.js";
23
23
  export type { DateRangeBound, DateRangeValue, ResolvedDateRangeFacet } from "./app/list-facets.js";
24
24
  export { buildDateRangePayload, clampDateRange, readDateRange, resolveDateRangeFacets, } from "./app/list-facets.js";
25
25
  export type { NavApi, NavigateOptions, NavProviderProps, NavRoute, NavTarget, ObjectTarget, ScreenTarget, } from "./app/nav.js";
@@ -33,6 +33,8 @@ export type { VariableChipsProps } from "./app/variable-chips.js";
33
33
  export { VariableChips } from "./app/variable-chips.js";
34
34
  export { evalVisibleWhen, type VisibleWhenVerdict } from "./app/visible-when.js";
35
35
  export { dispatcherErrorText, WriteFailedError } from "./app/write-failed-error.js";
36
+ export type { ErrorBoundaryFallbackProps, ErrorBoundaryProps, } from "./components/error-boundary.js";
37
+ export { ErrorBoundary } from "./components/error-boundary.js";
36
38
  export { RelatedListSection } from "./components/related-list-section.js";
37
39
  export type { RenderEditAction, RenderEditChangeState, RenderEditControls, RenderEditProps, } from "./components/render-edit.js";
38
40
  export { RenderEdit } from "./components/render-edit.js";
@@ -73,7 +75,7 @@ export { createStaticLocaleResolver, FormalityProvider, LocaleProvider, mergeTra
73
75
  export { kumikoDefaultTranslations } from "./i18n-defaults.js";
74
76
  export { InsideDrawerProvider, useInsideDrawer } from "./inside-drawer.js";
75
77
  export { PageHeaderCompactProvider, PageHeaderSlotAvailableProvider, usePageHeaderCompact, usePageHeaderSlotAvailable, } from "./page-header-slot.js";
76
- export type { ActionMenuItemSpec, ActionOverflowMenuProps, AppPrimitives, BannerProps, ButtonProps, CardOptions, CardProps, CardSlots, CopyButtonProps, CorePrimitives, DataTableDateRangeFacet, DataTableFacet, DataTableFacetChip, DataTableProps, DataTableRowAction, DataTableRowActionMode, DataTableRowGrouping, DataTableSort, DataTableSortDir, DialogProps, DrawerProps, EmbeddedListCellType, EmbeddedListColumn, EmbeddedListInputProps, EmbeddedListTotal, FieldCellWidth, FieldProps, FillContainerProps, FooterActionMarker, FooterActionRole, FormProps, FormSectionNavItem, FormWidth, GridCellProps, GridProps, HeadingProps, InputProps, JsonViewProps, LightboxImage, LightboxProps, LinkProps, MetricBandProps, MetricProps, ModalProps, PageHeaderProps, PrimitivesProviderProps, PrimitivesRegistry, ProgressProps, ProgressTone, PromoPanelAction, PromoPanelProps, RuntimeRenderer, SecretRevealProps, SecretRevealValue, SectionProps, ShareButtonProps, StatusBadgeProps, StatusTone, StepBarProps, StickyActionBarProps, StickyPrimaryActionMarker, TabsProps, TextProps, TrailingInputAction, WizardStepGroupProps, } from "./primitives.js";
78
+ export type { ActionMenuItemSpec, ActionOverflowMenuProps, AppPrimitives, BannerProps, ButtonProps, CardOptions, CardProps, CardSlots, CopyButtonProps, CorePrimitives, DataTableDateRangeFacet, DataTableFacet, DataTableFacetChip, DataTableProps, DataTableRowAction, DataTableRowActionMode, DataTableRowGrouping, DataTableSort, DataTableSortDir, DialogProps, DrawerProps, EmbeddedListCellType, EmbeddedListColumn, EmbeddedListInputProps, EmbeddedListTotal, FieldCellWidth, FieldProps, FillContainerProps, FooterActionMarker, FooterActionRole, FormProps, FormSectionNavItem, FormWidth, GridCellProps, GridProps, HeadingProps, InputProps, JsonViewProps, LightboxImage, LightboxProps, LinkProps, MetricBandProps, MetricProps, ModalProps, PageHeaderProps, PickedFile, PrimitivesProviderProps, PrimitivesRegistry, ProgressProps, ProgressTone, PromoPanelAction, PromoPanelProps, RuntimeRenderer, SecretRevealProps, SecretRevealValue, SectionProps, ShareButtonProps, StatusBadgeProps, StatusTone, StepBarProps, StickyActionBarProps, StickyPrimaryActionMarker, TabsProps, TextProps, TrailingInputAction, WizardStepGroupProps, } from "./primitives.js";
77
79
  export { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, useOptionalPrimitives, usePrimitives, } from "./primitives.js";
78
80
  export type { SessionEndedSignal, SessionEndedSignalProviderProps, } from "./session/session-ended.js";
79
81
  export { createSessionEndedSignal, isSessionEndedError, SESSION_ENDED_ERROR_CODES, SessionEndedSignalProvider, useSessionEndedSignal, withSessionEndedDetection, } from "./session/session-ended.js";
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ export { DashboardBodyProvider, useDashboardBody } from "./app/dashboard-body.js
19
19
  export { ExtensionFormRegistryProvider, useExtensionFormSubmit, } from "./app/extension-form-submit.js";
20
20
  export { ExtensionSectionsProvider, extensionSectionName, useExtensionSectionComponent, } from "./app/extension-sections.js";
21
21
  export { isAppSchema, toAppSchema } from "./app/feature-schema.js";
22
- export { KumikoScreen, qualifyNavId, qualifyScreenId } from "./app/kumiko-screen.js";
22
+ export { KumikoScreen, qualifyNavId, qualifyScreenId, ScreenMissingState, } from "./app/kumiko-screen.js";
23
23
  export { buildDateRangePayload, clampDateRange, readDateRange, resolveDateRangeFacets, } from "./app/list-facets.js";
24
24
  export { formatPath, hasDetailScreen, NavProvider, parsePath, resolveTarget, useNav, useNavigateWithInitialValues, } from "./app/nav.js";
25
25
  export { lastSegment } from "./app/qn.js";
@@ -28,6 +28,7 @@ export { useEmbeddedScreen } from "./app/use-embedded-screen.js";
28
28
  export { VariableChips } from "./app/variable-chips.js";
29
29
  export { evalVisibleWhen } from "./app/visible-when.js";
30
30
  export { dispatcherErrorText, WriteFailedError } from "./app/write-failed-error.js";
31
+ export { ErrorBoundary } from "./components/error-boundary.js";
31
32
  export { RelatedListSection } from "./components/related-list-section.js";
32
33
  export { RenderEdit } from "./components/render-edit.js";
33
34
  export { needsActionConfirm } from "./components/render-edit-action-button.js";
@@ -180,6 +180,14 @@ export type TrailingInputAction = {
180
180
  readonly onPress: () => void;
181
181
  readonly testId?: string;
182
182
  };
183
+ /** Platform-neutral view of a picked file; the DOM `File` satisfies it. */
184
+ export type PickedFile = {
185
+ readonly name: string;
186
+ readonly size: number;
187
+ readonly type: string;
188
+ readonly webkitRelativePath: string;
189
+ readonly arrayBuffer: () => Promise<ArrayBuffer>;
190
+ };
183
191
  export type InputProps = {
184
192
  readonly kind: "text";
185
193
  readonly id: string;
@@ -318,6 +326,14 @@ export type InputProps = {
318
326
  /** Only for kind:"image" — forwarded to the file input's `capture`
319
327
  * attribute so a phone opens the camera instead of the file picker. */
320
328
  readonly capture?: "environment" | "user";
329
+ /** Web-only folder picker: the picked files (with `webkitRelativePath`)
330
+ * go to `onFilesPicked` and nothing is uploaded. Native impls ignore it. */
331
+ readonly directory?: boolean;
332
+ readonly onFilesPicked?: (files: readonly PickedFile[]) => void;
333
+ /** Translated label of the pick button; absent → the upload/change default. */
334
+ readonly pickLabel?: string;
335
+ /** Hidden file input gets `${testId}-input`, the pick button `${testId}-pick`. */
336
+ readonly testId?: string;
321
337
  } | {
322
338
  readonly kind: "date";
323
339
  readonly id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer",
3
- "version": "0.365.1",
3
+ "version": "0.367.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.365.1",
19
- "@cosmicdrift/kumiko-headless": "0.365.1",
20
- "@cosmicdrift/kumiko-types": "0.365.1",
18
+ "@cosmicdrift/kumiko-framework": "0.367.0",
19
+ "@cosmicdrift/kumiko-headless": "0.367.0",
20
+ "@cosmicdrift/kumiko-types": "0.367.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.365.1"
31
+ "@cosmicdrift/kumiko-locale-de": "0.367.0"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
package/src/changes.json CHANGED
@@ -1,4 +1,23 @@
1
1
  [
2
+ {
3
+ "version": "0.367.0",
4
+ "type": "fix",
5
+ "title": "projectionDetail no longer shows an empty type-title block above the record",
6
+ "detail": "The projectionDetail shim dropped recordTitleField, so the duplicate-title fix never applied to real projectionDetail screens. The synthesized edit screen now carries it, and the page header shows the record title once."
7
+ },
8
+ {
9
+ "version": "0.367.0",
10
+ "type": "fix",
11
+ "title": "A route to a screen missing from the schema shows an empty state",
12
+ "detail": "A tenant-disabled feature, stale link or typo no longer falls back to the first feature or shows \"Screen not found: <qn>\". The app renders \"This page is not available\" with a button to the landing screen; the qualified name only goes to the console. New i18n keys kumiko.screen-missing.title, .hint and .home (en, de, es). The access-denied banner of KumikoScreen and the toolbar drawer is now translated too (kumiko.screen-access-denied.title) and no longer prints the qualified name.",
13
+ "migration": "e2e checks on the text \"Screen not found\" should use the testId kumiko-screen-not-found; the testIds are unchanged."
14
+ },
15
+ {
16
+ "version": "0.367.0",
17
+ "type": "fix",
18
+ "title": "projectionDetail drops the duplicate title block",
19
+ "detail": "A screen with recordTitleField no longer repeats the screen title above the form when the page header already shows the record title."
20
+ },
2
21
  {
3
22
  "version": "0.365.0",
4
23
  "type": "fix",