@cosmicdrift/kumiko-renderer 0.366.0 → 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.
- package/dist/app/kumiko-screen.d.ts +7 -0
- package/dist/app/kumiko-screen.js +28 -5
- package/dist/app/projection-detail-shim.js +1 -0
- package/dist/components/render-edit.js +8 -1
- package/dist/i18n-defaults.js +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/primitives.d.ts +16 -0
- package/package.json +5 -5
- package/src/changes.json +19 -0
|
@@ -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
|
|
73
|
+
return _jsx(ScreenMissingState, { screenQn: qn });
|
|
75
74
|
}
|
|
76
75
|
if (!screenAccessAllows(screen.access, userRoles)) {
|
|
77
|
-
return (
|
|
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,
|
|
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 ? (
|
|
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 }),
|
|
@@ -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 &&
|
|
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 && {
|
package/dist/i18n-defaults.js
CHANGED
|
@@ -135,6 +135,10 @@ 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",
|
|
138
142
|
"kumiko.screen-error.title": "This screen failed to render.",
|
|
139
143
|
"kumiko.screen-error.detail": "Screen: {screen}. {message}",
|
|
140
144
|
"kumiko.screen-error.screen": "Screen: {screen}",
|
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";
|
|
@@ -75,7 +75,7 @@ export { createStaticLocaleResolver, FormalityProvider, LocaleProvider, mergeTra
|
|
|
75
75
|
export { kumikoDefaultTranslations } from "./i18n-defaults.js";
|
|
76
76
|
export { InsideDrawerProvider, useInsideDrawer } from "./inside-drawer.js";
|
|
77
77
|
export { PageHeaderCompactProvider, PageHeaderSlotAvailableProvider, usePageHeaderCompact, usePageHeaderSlotAvailable, } from "./page-header-slot.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, 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";
|
|
79
79
|
export { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, useOptionalPrimitives, usePrimitives, } from "./primitives.js";
|
|
80
80
|
export type { SessionEndedSignal, SessionEndedSignalProviderProps, } from "./session/session-ended.js";
|
|
81
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";
|
package/dist/primitives.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
19
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
20
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
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.
|
|
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",
|