@cosmicdrift/kumiko-renderer 0.341.0 → 0.343.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.
@@ -42,6 +42,8 @@ import { buildDefaultEditRowAction, buildDrawerRowAction, buildProjectionRowActi
42
42
  import { findEditScreenFor, navigateTargetAllows, screenAccessAllows } from "./screen-access.js";
43
43
  import { SecretMintBody } from "./secret-mint-body.js";
44
44
  import { SecretsEditBody } from "./secrets-edit-body.js";
45
+ import { useEmbeddedScreen } from "./use-embedded-screen.js";
46
+ import { evalVisibleWhen } from "./visible-when.js";
45
47
  import { dispatcherErrorText, WriteFailedError } from "./write-failed-error.js";
46
48
  export { qualifyScreenId };
47
49
  /** Mirrors qualifyScreenId for nav QNs (registry form `<feature>:nav:<short-id>`).
@@ -66,7 +68,16 @@ export function KumikoScreen({ schema, qn, translate, entityId, onRowClick, onCo
66
68
  if (!screenAccessAllows(screen.access, userRoles)) {
67
69
  return (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-screen-access-denied", children: ["Access denied: ", _jsx(Text, { variant: "code", children: qn })] }));
68
70
  }
69
- const body = renderScreenBody({ schema, screen, translate, entityId, onRowClick, onCopyLink });
71
+ const screenBody = renderScreenBody({
72
+ schema,
73
+ screen,
74
+ translate,
75
+ entityId,
76
+ onRowClick,
77
+ onCopyLink,
78
+ });
79
+ // Gate every entry (incl. direct URL): the body element is only mounted once the condition holds.
80
+ const body = screen.visibleWhen !== undefined ? (_jsx(ScreenVisibilityGate, { featureName: schema.featureName, visibleWhen: screen.visibleWhen, fallback: screen.fallback, translate: translate, children: screenBody })) : (screenBody);
70
81
  // An embedded screen (e.g. a dashboard panel) keeps its parent's host and
71
82
  // must not portal its page header into the shell that hosts the outer screen.
72
83
  if (outerHost !== undefined) {
@@ -74,6 +85,29 @@ export function KumikoScreen({ schema, qn, translate, entityId, onRowClick, onCo
74
85
  }
75
86
  return _jsx(ReturnHostProvider, { value: ownHost, children: body });
76
87
  }
88
+ function ScreenVisibilityGate({ featureName, visibleWhen, fallback, translate, children, }) {
89
+ const { Banner } = usePrimitives();
90
+ const t = useTranslation();
91
+ const result = useQuery(visibleWhen.query, {}, { live: true });
92
+ const verdict = evalVisibleWhen(visibleWhen, result);
93
+ if (verdict === "visible")
94
+ return children;
95
+ if (verdict === "loading") {
96
+ return (_jsx(Banner, { padded: true, variant: "loading", testId: "kumiko-screen-loading", children: "Loading\u2026" }));
97
+ }
98
+ if (fallback !== undefined) {
99
+ return _jsx(FallbackScreen, { featureName: featureName, fallback: fallback, translate: translate });
100
+ }
101
+ return (_jsx(Banner, { padded: true, variant: "info", testId: "kumiko-screen-unavailable", children: t("kumiko.screen.unavailable") }));
102
+ }
103
+ function FallbackScreen({ featureName, fallback, translate, }) {
104
+ const target = useEmbeddedScreen(featureName, fallback);
105
+ const { Banner, Text } = usePrimitives();
106
+ if (target === undefined) {
107
+ return (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-screen-not-found", children: ["Screen not found: ", _jsx(Text, { variant: "code", children: fallback })] }));
108
+ }
109
+ return _jsx(KumikoScreen, { schema: target.schema, qn: target.qn, translate: translate });
110
+ }
77
111
  function renderScreenBody({ schema, screen, translate, entityId, onRowClick, onCopyLink, }) {
78
112
  switch (screen.type) {
79
113
  case "entityEdit":
@@ -0,0 +1,4 @@
1
+ import type { DashboardPanelVisibility } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import type { UseQueryResult } from "../hooks/use-query.js";
3
+ export type VisibleWhenVerdict = "loading" | "visible" | "hidden" | "error";
4
+ export declare function evalVisibleWhen(visibleWhen: DashboardPanelVisibility, result: UseQueryResult<Readonly<Record<string, unknown>>> | undefined): VisibleWhenVerdict;
@@ -0,0 +1,11 @@
1
+ // Shared by dashboard screen panels and screen-level visibleWhen. Anything but a
2
+ // positive match is non-visible (fail-closed); `error` is split out so panels can offer a retry.
3
+ export function evalVisibleWhen(visibleWhen, result) {
4
+ if (result?.error)
5
+ return "error";
6
+ if (result === undefined)
7
+ return "hidden";
8
+ if (result.data === null)
9
+ return result.loading ? "loading" : "hidden";
10
+ return result.data[visibleWhen.field] === visibleWhen.eq ? "visible" : "hidden";
11
+ }
@@ -106,6 +106,7 @@ export const kumikoDefaultTranslations = {
106
106
  "kumiko.dashboard.panel.error.retry": "Try again",
107
107
  "kumiko.dashboard.panel.error.title": "{label} could not be loaded",
108
108
  "kumiko.dashboard.time-range": "Time range",
109
+ "kumiko.screen.unavailable": "This page is not available right now.",
109
110
  "kumiko.dashboard.today": "Today",
110
111
  "kumiko.dashboard.updated-at": "As of {time}",
111
112
  "kumiko.app-boot.loading": "Loading…",
package/dist/index.d.ts CHANGED
@@ -31,6 +31,7 @@ export type { EmbeddedScreenTarget } from "./app/use-embedded-screen.js";
31
31
  export { useEmbeddedScreen } from "./app/use-embedded-screen.js";
32
32
  export type { VariableChipsProps } from "./app/variable-chips.js";
33
33
  export { VariableChips } from "./app/variable-chips.js";
34
+ export { evalVisibleWhen, type VisibleWhenVerdict } from "./app/visible-when.js";
34
35
  export { dispatcherErrorText, WriteFailedError } from "./app/write-failed-error.js";
35
36
  export { RelatedListSection } from "./components/related-list-section.js";
36
37
  export type { RenderEditAction, RenderEditChangeState, RenderEditControls, RenderEditProps, } from "./components/render-edit.js";
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ export { lastSegment } from "./app/qn.js";
26
26
  export { navigateToReturn, navigateToReturnOr, navigateWithReturnTo, RETURN_TO_PARAM, ReturnHostProvider, resolveReturnTarget, splitReturnTo, useReturnHost, useReturnTarget, } from "./app/return-to.js";
27
27
  export { useEmbeddedScreen } from "./app/use-embedded-screen.js";
28
28
  export { VariableChips } from "./app/variable-chips.js";
29
+ export { evalVisibleWhen } from "./app/visible-when.js";
29
30
  export { dispatcherErrorText, WriteFailedError } from "./app/write-failed-error.js";
30
31
  export { RelatedListSection } from "./components/related-list-section.js";
31
32
  export { RenderEdit } from "./components/render-edit.js";
@@ -107,8 +107,12 @@ export type BannerProps = {
107
107
  /** "error" for alerts (conflict, network error), "info" for neutral
108
108
  * placeholders (not-found, loading), "loading" for load states, "warning"
109
109
  * for a non-blocking but attention-grabbing notice (e.g. secretMint's
110
- * "this is shown only once"). */
111
- readonly variant?: "error" | "info" | "loading" | "warning";
110
+ * "this is shown only once"), "primary" for a call-to-action notice (neutral
111
+ * background, accent in the primary color; pair it with `title` and `actions`). */
112
+ readonly variant?: "error" | "info" | "loading" | "warning" | "primary";
113
+ /** Bold heading above `children`, inside the text column. */
114
+ readonly title?: ReactNode;
115
+ readonly titleTestId?: string;
112
116
  readonly children: ReactNode;
113
117
  /** Optional — weitere Knöpfe/Elemente rechts vom Text (z.B. "Neu
114
118
  * laden"). Inline, nicht als eigener Block. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer",
3
- "version": "0.341.0",
3
+ "version": "0.343.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.341.0",
19
- "@cosmicdrift/kumiko-headless": "0.341.0",
20
- "@cosmicdrift/kumiko-types": "0.341.0",
18
+ "@cosmicdrift/kumiko-framework": "0.343.0",
19
+ "@cosmicdrift/kumiko-headless": "0.343.0",
20
+ "@cosmicdrift/kumiko-types": "0.343.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.341.0"
31
+ "@cosmicdrift/kumiko-locale-de": "0.343.0"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.343.0",
4
+ "type": "improvement",
5
+ "title": "Screens take visibleWhen and fallback",
6
+ "detail": "Any screen can gate itself on a query field with `visibleWhen`, including on direct URL access. Unmet or failed conditions render the `fallback` screen or a standard notice instead of the content. New i18n key: `kumiko.screen.unavailable`."
7
+ },
8
+ {
9
+ "version": "0.342.0",
10
+ "type": "improvement",
11
+ "title": "Banner gets title, titleTestId and variant \"primary\"",
12
+ "detail": "Banner takes title, titleTestId and variant \"primary\"\n`BannerProps` gets `title` (bold heading above the text), `titleTestId` and the variant `\"primary\"` for call-to-action notices: left accent in the primary color, neutral background, `actions` right of the text on wide screens and below it on narrow ones. Existing variants render unchanged."
13
+ },
2
14
  {
3
15
  "version": "0.341.0",
4
16
  "type": "fix",