@cosmicdrift/kumiko-renderer 0.335.0 → 0.336.1

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.
@@ -57,9 +57,9 @@ export type ExtensionSectionProps = {
57
57
  };
58
58
  /** Only set for extension steps of a `layout.mode: "wizard"` entityEdit
59
59
  * editing an existing record (update mode); undefined everywhere else.
60
- * Call it (typically from an effect) with whether the step already holds
61
- * its data, so the step bar shows the step as done. Repeated calls with
62
- * the same value are cheap. */
60
+ * Report through `useReportStepComplete(reportStepComplete, complete)`
61
+ * (`null` while the step's data loads) with whether the step already holds
62
+ * its data, so the step bar shows the step as done. */
63
63
  readonly reportStepComplete?: (complete: boolean) => void;
64
64
  };
65
65
  export type ExtensionSectionComponent = ComponentType<ExtensionSectionProps>;
@@ -959,9 +959,10 @@ function DrawerHost({ schema, drawerAction, drawerScreen, drawerInitialValues, u
959
959
  closeAndReset();
960
960
  }, [hasUnsavedInput, closeAndReset]);
961
961
  // Drawer is an optional Core-Primitive (additive rollout) — same "skip +
962
- // warn once" precedent as rowActions without a mounted DispatcherProvider
963
- // above, instead of crashing when a web app hasn't upgraded its
964
- // createKumikoApp wiring yet.
962
+ // warn" precedent as rowActions without a mounted DispatcherProvider above,
963
+ // instead of crashing when a web app hasn't upgraded its createKumikoApp
964
+ // wiring yet. The warning repeats per mount and whenever the action changes.
965
+ // Dev-time setup error, so no dedupe.
965
966
  useEffect(() => {
966
967
  if (drawerAction !== null && Drawer === undefined) {
967
968
  // biome-ignore lint/suspicious/noConsole: dev-warning for a setup error
@@ -164,10 +164,9 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
164
164
  }, [ownRefetch, onAfterWrite]);
165
165
  const onFilterChange = useCallback((field, values) => setFilters((prev) => ({ ...prev, [field]: values })), []);
166
166
  const onFilterReset = useCallback(() => setFilters({}), []);
167
- // Sorted client-side over the already-loaded rows — this section has no
168
- // pager (see `payload` above: a one-shot fetch, no cursor/offset), so the
169
- // loaded set already IS the full display set and there is no "other page"
170
- // a client-side sort could misleadingly hide (fw#2722).
167
+ // Sorted client-side over the already-loaded rows only. The section has no
168
+ // pager (one-shot fetch); when that fetch is truncated (`nextCursor` set) the
169
+ // truncation banner below tells the user the sorted set is partial.
171
170
  const [sort, setSort] = useState(section.defaultSort ?? null);
172
171
  const sortAccessors = useMemo(() => {
173
172
  const accessors = {};
@@ -14,7 +14,7 @@ import { useForm } from "../hooks/use-form.js";
14
14
  import { useTranslation } from "../i18n.js";
15
15
  import { useInsideDrawer } from "../inside-drawer.js";
16
16
  import { usePageHeaderCompact, usePageHeaderSlotAvailable } from "../page-header-slot.js";
17
- import { STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, usePrimitives, } from "../primitives.js";
17
+ import { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, usePrimitives, } from "../primitives.js";
18
18
  import { GridCellForField } from "./grid-cell-for-field.js";
19
19
  import { RelatedListSection } from "./related-list-section.js";
20
20
  import { filterEditSections, hasEditableSection, hasIssueWithoutRenderedField, resolveExtensionEntityId, shouldNotifyCaller, } from "./render-edit-logic.js";
@@ -1107,7 +1107,8 @@ export function RenderEdit(props) {
1107
1107
  ? 1
1108
1108
  : 0;
1109
1109
  const isSettingsList = screen.layout.variant === "settings-list" && hideSectionTitles !== true;
1110
- const formActions = (_jsxs(_Fragment, { children: [showWizardBack && (_jsx(Button, { type: "button", variant: "secondary", icon: "chevron-left", onClick: handleWizardBack, testId: "render-edit-wizard-back", children: translate("kumiko.actions.back") })), footerSlot !== undefined && (_jsx(EditSlotMount, { slot: footerSlot, slotName: "footer", screenId: screen.id, entityName: vm.entityName, entityId: resolveExtensionEntityId(entityIdProp, vm.id), values: snapshot.values, hasUnsavedChanges: hasUnsavedInput, wizardStep: isWizard ? { index: currentStep, isLast: isLastWizardStep } : undefined, ...(screen.slots?.footerPrimary === true && { [STICKY_PRIMARY_ACTION_PROP]: true }) })), isWizard && !isLastWizardStep && showsSubmit && !isCreateMode && (_jsx(Button, { type: "button", variant: "ghost", disabled: isSubmitting || disabled, onClick: () => void handleSubmit({ saveNow: true }), testId: "render-edit-wizard-save-close", [STICKY_PRIMARY_ACTION_PROP]: true, children: translate("kumiko.wizard.save-and-close") })), showWizardNext && (_jsx(Button, { type: "submit", variant: "primary", iconEnd: "chevron-right", testId: "render-edit-wizard-next", children: nextStepTitle !== undefined
1110
+ const formActions = (_jsxs(_Fragment, { children: [showWizardBack && (_jsx(Button, { type: "button", variant: "secondary", icon: "chevron-left", onClick: handleWizardBack, testId: "render-edit-wizard-back", [FOOTER_ACTION_ROLE_PROP]: "back", children: translate("kumiko.actions.back") })), footerSlot !== undefined && (_jsx(EditSlotMount, { slot: footerSlot, slotName: "footer", screenId: screen.id, entityName: vm.entityName, entityId: resolveExtensionEntityId(entityIdProp, vm.id), values: snapshot.values, hasUnsavedChanges: hasUnsavedInput, wizardStep: isWizard ? { index: currentStep, isLast: isLastWizardStep } : undefined, ...(screen.slots?.footerPrimary === true && { [STICKY_PRIMARY_ACTION_PROP]: true }) })), isWizard && !isLastWizardStep && showsSubmit && !isCreateMode && (_jsx(Button, { type: "button", variant: "ghost", disabled: isSubmitting || disabled, onClick: () => void handleSubmit({ saveNow: true }), testId: "render-edit-wizard-save-close", [STICKY_PRIMARY_ACTION_PROP]: true, children: translate("kumiko.wizard.save-and-close") })), showWizardNext && (_jsx(Button, { type: "submit", variant: "primary", iconEnd: "chevron-right", testId: "render-edit-wizard-next", [FOOTER_ACTION_ROLE_PROP]: "primary",
1111
+ [NARROW_LABEL_PROP]: translate("kumiko.actions.next"), children: nextStepTitle !== undefined
1111
1112
  ? translate("kumiko.wizard.next-with-title", { title: nextStepTitle })
1112
1113
  : translate("kumiko.actions.next") })), showSubmit &&
1113
1114
  footerActions?.map((action) => (_jsx(Button, { type: "button", variant: action.variant ?? "secondary", ...(action.icon !== undefined && { icon: action.icon }), disabled: isSubmitting || disabled, onClick: () => {
@@ -100,8 +100,7 @@ export function WriteFormSection({ section, featureName, translate, hideTitle, o
100
100
  headerActions: titleRowActions,
101
101
  }, testId: `write-form-${section.title ?? section.handler}`, children: content }));
102
102
  }
103
- // Routed through Section's `actions` slot (same mechanism render-edit.tsx
104
- // uses via Form's `actions`) so the button gets the established right-
105
- // aligned footer treatment instead of stretching full-width inline.
103
+ // Submit plus caller actions share the title row's actions slot (submit is
104
+ // lifted into the footer instead when onFooterAction is set).
106
105
  return (_jsx(Section, { ...(section.title !== undefined && { title: section.title }), ...(section.description !== undefined && { subtitle: section.description }), ...(section.icon !== undefined && { icon: section.icon }), actions: titleRowActions, testId: `write-form-${section.title ?? section.handler}`, children: content }));
107
106
  }
@@ -0,0 +1,2 @@
1
+ import type { ExtensionSectionProps } from "../app/extension-sections.js";
2
+ export declare function useReportStepComplete(reportStepComplete: ExtensionSectionProps["reportStepComplete"], complete: boolean | null): void;
@@ -0,0 +1,13 @@
1
+ import { useEffect } from "react";
2
+ // Lets an extension wizard step report whether it already holds its data
3
+ // without a raw useEffect in app code (the no-raw-hooks guard forbids those).
4
+ // `complete === null` means the step's data is still loading: nothing is
5
+ // reported, so a loading step never flashes "not done" over a passed-via-Next
6
+ // state. Outside update-mode wizards `reportStepComplete` is undefined and the
7
+ // hook does nothing.
8
+ export function useReportStepComplete(reportStepComplete, complete) {
9
+ useEffect(() => {
10
+ if (reportStepComplete !== undefined && complete !== null)
11
+ reportStepComplete(complete);
12
+ }, [reportStepComplete, complete]);
13
+ }
package/dist/index.d.ts CHANGED
@@ -61,6 +61,7 @@ export type { UseMutationResult } from "./hooks/use-mutation.js";
61
61
  export { useMutation } from "./hooks/use-mutation.js";
62
62
  export type { UseQueryOptions, UseQueryResult } from "./hooks/use-query.js";
63
63
  export { entityFromQueryType, useQuery } from "./hooks/use-query.js";
64
+ export { useReportStepComplete } from "./hooks/use-report-step-complete.js";
64
65
  export { useStore, useStoreSelector } from "./hooks/use-store.js";
65
66
  export type { StreamStatus, UseStreamHandlerOptions, UseStreamHandlerResult, } from "./hooks/use-stream-handler.js";
66
67
  export { useStreamHandler } from "./hooks/use-stream-handler.js";
@@ -69,8 +70,8 @@ export { createStaticLocaleResolver, FormalityProvider, LocaleProvider, mergeTra
69
70
  export { kumikoDefaultTranslations } from "./i18n-defaults.js";
70
71
  export { InsideDrawerProvider, useInsideDrawer } from "./inside-drawer.js";
71
72
  export { PageHeaderCompactProvider, PageHeaderSlotAvailableProvider, usePageHeaderCompact, usePageHeaderSlotAvailable, } from "./page-header-slot.js";
72
- 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, FormProps, FormSectionNavItem, FormWidth, GridCellProps, GridProps, HeadingProps, InputProps, JsonViewProps, 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";
73
- export { PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, usePrimitives, } from "./primitives.js";
73
+ 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, 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";
74
+ export { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, usePrimitives, } from "./primitives.js";
74
75
  export type { SessionEndedSignal, SessionEndedSignalProviderProps, } from "./session/session-ended.js";
75
76
  export { createSessionEndedSignal, isSessionEndedError, SESSION_ENDED_ERROR_CODES, SessionEndedSignalProvider, useSessionEndedSignal, withSessionEndedDetection, } from "./session/session-ended.js";
76
77
  export { buildWhatsAppShareUrl } from "./share-links.js";
package/dist/index.js CHANGED
@@ -44,13 +44,14 @@ export { useForm } from "./hooks/use-form.js";
44
44
  export { useListUrlState } from "./hooks/use-list-url-state.js";
45
45
  export { useMutation } from "./hooks/use-mutation.js";
46
46
  export { entityFromQueryType, useQuery } from "./hooks/use-query.js";
47
+ export { useReportStepComplete } from "./hooks/use-report-step-complete.js";
47
48
  export { useStore, useStoreSelector } from "./hooks/use-store.js";
48
49
  export { useStreamHandler } from "./hooks/use-stream-handler.js";
49
50
  export { createStaticLocaleResolver, FormalityProvider, LocaleProvider, mergeTranslations, translationsByLocaleFromKeys, useFormality, useLocale, useOptionalLocale, useOptionalTranslation, useTranslation, } from "./i18n.js";
50
51
  export { kumikoDefaultTranslations } from "./i18n-defaults.js";
51
52
  export { InsideDrawerProvider, useInsideDrawer } from "./inside-drawer.js";
52
53
  export { PageHeaderCompactProvider, PageHeaderSlotAvailableProvider, usePageHeaderCompact, usePageHeaderSlotAvailable, } from "./page-header-slot.js";
53
- export { PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, usePrimitives, } from "./primitives.js";
54
+ export { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, usePrimitives, } from "./primitives.js";
54
55
  export { createSessionEndedSignal, isSessionEndedError, SESSION_ENDED_ERROR_CODES, SessionEndedSignalProvider, useSessionEndedSignal, withSessionEndedDetection, } from "./session/session-ended.js";
55
56
  export { buildWhatsAppShareUrl } from "./share-links.js";
56
57
  export { sortByAccessor } from "./sort-by-accessor.js";
@@ -14,6 +14,16 @@ export type DataAttributes = Readonly<Record<`data-${string}`, string>>;
14
14
  * split can't inspect (see `ScreenSlots.footerPrimary`). */
15
15
  export declare const STICKY_PRIMARY_ACTION_PROP: "kumikoStickyPrimaryAction";
16
16
  export type StickyPrimaryActionMarker = Partial<Record<typeof STICKY_PRIMARY_ACTION_PROP, boolean>>;
17
+ export type FooterActionRole = "back" | "primary";
18
+ /** Tells the pinned phone footer which node is Back and which is the primary
19
+ * action, for buttons that are not `type="submit"` (e.g. the wizard Back). */
20
+ export declare const FOOTER_ACTION_ROLE_PROP: "kumikoFooterActionRole";
21
+ /** Short label a footer action uses instead of its children below `sm`. */
22
+ export declare const NARROW_LABEL_PROP: "kumikoNarrowLabel";
23
+ export type FooterActionMarker = {
24
+ readonly [FOOTER_ACTION_ROLE_PROP]?: FooterActionRole;
25
+ readonly [NARROW_LABEL_PROP]?: string;
26
+ };
17
27
  /** Standard-Button. `loading` zeigt einen Spinner statt der Children
18
28
  * und sollte mit `disabled` kombiniert werden, wenn die Action wirklich
19
29
  * blockiert bis das Loading durch ist (z.B. async submit). Native-
@@ -1157,7 +1167,9 @@ export type ProgressProps = {
1157
1167
  * buttons (every non-current step with `selectableSteps: "all"`); the caller
1158
1168
  * owns the validation gate for forward jumps. Implementations render a
1159
1169
  * narrow-viewport fallback showing `compactLabel` instead (caller
1160
- * supplies it pre-translated, e.g. "Step 2 of 5 · Industry") — which of
1170
+ * supplies it pre-translated, e.g. "Step 2 of 5 · Industry"). With
1171
+ * `onStepSelect` and `selectableSteps: "all"` that fallback is a dropdown
1172
+ * listing all steps so narrow viewports can jump too — which of
1161
1173
  * the two is visible is a responsive layout choice owned by the
1162
1174
  * implementation, not this contract. */
1163
1175
  export type StepBarProps = {
@@ -1169,7 +1181,8 @@ export type StepBarProps = {
1169
1181
  * index is below `currentIndex`. The current step is never done. */
1170
1182
  readonly doneSteps?: readonly boolean[];
1171
1183
  /** "done" (default): only done steps are jump targets. "all": every
1172
- * non-current step is, when `onStepSelect` is set. */
1184
+ * non-current step is, when `onStepSelect` is set; the compact label then
1185
+ * becomes a step picker. */
1173
1186
  readonly selectableSteps?: "done" | "all";
1174
1187
  /** "label" (default) swaps the step row for `compactLabel` on narrow
1175
1188
  * viewports; "steps" keeps the row there too — for short wizards whose
@@ -6,6 +6,11 @@ import { createContext, useContext, } from "react";
6
6
  * footer-slot mount, whose actual rendered button is opaque app code the
7
7
  * split can't inspect (see `ScreenSlots.footerPrimary`). */
8
8
  export const STICKY_PRIMARY_ACTION_PROP = "kumikoStickyPrimaryAction";
9
+ /** Tells the pinned phone footer which node is Back and which is the primary
10
+ * action, for buttons that are not `type="submit"` (e.g. the wizard Back). */
11
+ export const FOOTER_ACTION_ROLE_PROP = "kumikoFooterActionRole";
12
+ /** Short label a footer action uses instead of its children below `sm`. */
13
+ export const NARROW_LABEL_PROP = "kumikoNarrowLabel";
9
14
  /** Teil-C action-icon collapse rule: a group of more than two actions where
10
15
  * every member carries an icon renders icon-only instead of wall-to-wall
11
16
  * text buttons; any icon-less member keeps the whole group on text so it
@@ -4,8 +4,8 @@ export type LiveEvent = {
4
4
  readonly data: {
5
5
  readonly id: string;
6
6
  readonly aggregateType: string;
7
+ readonly eventType: string;
7
8
  readonly version: number;
8
- readonly payload: unknown;
9
9
  readonly createdAt: string;
10
10
  };
11
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer",
3
- "version": "0.335.0",
3
+ "version": "0.336.1",
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.335.0",
19
- "@cosmicdrift/kumiko-headless": "0.335.0",
20
- "@cosmicdrift/kumiko-types": "0.335.0",
18
+ "@cosmicdrift/kumiko-framework": "0.336.1",
19
+ "@cosmicdrift/kumiko-headless": "0.336.1",
20
+ "@cosmicdrift/kumiko-types": "0.336.1",
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.335.0"
31
+ "@cosmicdrift/kumiko-locale-de": "0.336.1"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
package/src/changes.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "version": "0.336.0",
4
+ "type": "breaking",
5
+ "title": "LiveEvent data has no payload, it carries eventType",
6
+ "detail": "`LiveEvent.data` is now `{ id, aggregateType, eventType, version, createdAt }`. The server sends signals only, so consumers refetch through a query.",
7
+ "migration": "Replace reads of `event.data.payload` with a query refetch. `useQuery({ live: true })` already does this and needs no change."
8
+ },
9
+ {
10
+ "version": "0.336.0",
11
+ "type": "improvement",
12
+ "title": "useReportStepComplete reports an extension wizard step's completeness without a raw useEffect",
13
+ "detail": "`useReportStepComplete(props.reportStepComplete, complete)` with `complete: boolean | null`. `null` reports nothing (data loading), a boolean is reported whenever it changes. Outside update-mode wizards `reportStepComplete` is undefined and the hook does nothing.",
14
+ "migration": "Extension steps that call `reportStepComplete` from a `useEffect` should switch to `useReportStepComplete(reportStepComplete, complete)`; app repos need this to pass the no-raw-hooks guard."
15
+ },
2
16
  {
3
17
  "version": "0.335.0",
4
18
  "type": "fix",