@cosmicdrift/kumiko-renderer-web 0.211.0 → 0.213.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer-web",
3
- "version": "0.211.0",
3
+ "version": "0.213.0",
4
4
  "description": "Web-platform bindings for @cosmicdrift/kumiko-renderer. HTML default-primitives, browser history-based navigation, EventSource-backed live events, and a one-call createKumikoApp that mounts the whole stack via react-dom.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -16,9 +16,9 @@
16
16
  "./styles.css": "./src/styles.css"
17
17
  },
18
18
  "dependencies": {
19
- "@cosmicdrift/kumiko-dispatcher-live": "0.211.0",
20
- "@cosmicdrift/kumiko-headless": "0.211.0",
21
- "@cosmicdrift/kumiko-renderer": "0.211.0",
19
+ "@cosmicdrift/kumiko-dispatcher-live": "0.213.0",
20
+ "@cosmicdrift/kumiko-headless": "0.213.0",
21
+ "@cosmicdrift/kumiko-renderer": "0.213.0",
22
22
  "@radix-ui/react-dialog": "^1.1.15",
23
23
  "@radix-ui/react-dropdown-menu": "^2.1.16",
24
24
  "@radix-ui/react-label": "^2.1.8",
@@ -64,7 +64,7 @@
64
64
  "@types/react-dom": "^19.2.3",
65
65
  "jsdom": "^29.1.1",
66
66
  "tailwindcss": "^4.3.0",
67
- "@cosmicdrift/kumiko-locale-de": "0.211.0"
67
+ "@cosmicdrift/kumiko-locale-de": "0.213.0"
68
68
  },
69
69
  "repository": {
70
70
  "type": "git",
@@ -166,6 +166,8 @@ const NAV_ICON_LUCIDE_CLASS: Readonly<Record<string, string>> = {
166
166
  palette: "lucide-palette",
167
167
  link: "lucide-link",
168
168
  share: "lucide-share-2",
169
+ send: "lucide-send",
170
+ "shield-check": "lucide-shield-check",
169
171
  };
170
172
 
171
173
  function expectNavIcons(container: HTMLElement, iconKeys: readonly string[]): void {
@@ -340,6 +342,30 @@ describe("NavTree", () => {
340
342
  expectNavIcons(container, ["palette", "link", "share"]);
341
343
  });
342
344
 
345
+ test("send und shield-check rendern Lucide-Icons", () => {
346
+ const schema = {
347
+ featureName: "app",
348
+ entities: {},
349
+ screens: [
350
+ { id: "delivery-log", type: "entityList", entity: "x", columns: [] },
351
+ { id: "profile-picker", type: "entityList", entity: "x", columns: [] },
352
+ ],
353
+ navs: [
354
+ { id: "delivery-log", label: "Log", screen: "delivery-log", order: 10, icon: "send" },
355
+ {
356
+ id: "profile-picker",
357
+ label: "Profile",
358
+ screen: "profile-picker",
359
+ order: 20,
360
+ icon: "shield-check",
361
+ },
362
+ ],
363
+ } as FeatureSchema;
364
+ const { container } = render(<NavTree schema={schema} />);
365
+ expectNavIcons(container, ["send", "shield-check"]);
366
+ expect(warnSpy).not.toHaveBeenCalled();
367
+ });
368
+
343
369
  test("unbekannter icon-Key fällt sauber auf den Dot zurück (kein svg), aber warnt sichtbar", () => {
344
370
  const schema = {
345
371
  featureName: "showcase",
@@ -48,6 +48,7 @@ import { UpdateChecker } from "../version/update-checker";
48
48
  import { createBrowserLocaleResolver } from "./browser-locale";
49
49
  import { type ClientFeatureDefinition, stackWrappers } from "./client-plugin";
50
50
  import { WebDashboardBody } from "./dashboard-body";
51
+ import { DocumentLangSync } from "./document-lang-sync";
51
52
  import { createBrowserDraftStorage } from "./draft-storage";
52
53
  import { useBrowserNavApi } from "./nav";
53
54
  import { NavProvidersProvider } from "./nav-providers-context";
@@ -426,6 +427,7 @@ export function createKumikoApp(options: CreateKumikoAppOptions = {}): { readonl
426
427
  const tree = (
427
428
  <TokensBoot>
428
429
  <LocaleProvider resolver={localeResolver} fallbackBundles={fallbackBundles}>
430
+ <DocumentLangSync resolver={localeResolver} />
429
431
  <PrimitivesProvider value={primitives}>
430
432
  <AppFeaturesProvider features={app.features}>
431
433
  <DispatcherProvider dispatcher={dispatcher}>
@@ -13,19 +13,20 @@ import { defaultPrimitives } from "../primitives";
13
13
  import { ToastProvider } from "../primitives/toast";
14
14
  import { createBrowserLocaleResolver } from "./browser-locale";
15
15
  import { type ClientFeatureDefinition, stackWrappers } from "./client-plugin";
16
+ import { DocumentLangSync } from "./document-lang-sync";
16
17
 
17
- // Apex-Surfaceder öffentliche Gegenpart zu createKumikoApp. Mountet eine
18
- // schlanke, schema-LOSE Provider-Chain (Locale + Primitives + Dispatcher +
19
- // feature-providers) und rendert genau einen anhand des URL-Pfads gewählten
20
- // Content. Bewusst KEIN Schema, KEINE Nav, KEIN KumikoScreen: die Surface ist
21
- // anonym erreichbar, ein __KUMIKO_SCHEMA__-Inject würde Admin-Nav/Topologie an
22
- // Besucher leaken (injectSchema:false ist hier struktureller Default, kein Flag).
18
+ // Apex surface the public counterpart to createKumikoApp. Mounts a thin,
19
+ // schema-LESS provider chain (locale + primitives + dispatcher +
20
+ // feature-providers) and renders exactly one content chosen by URL path.
21
+ // Deliberately NO schema, NO nav, NO KumikoScreen: the surface is reachable
22
+ // anonymously, and a __KUMIKO_SCHEMA__ inject would leak admin nav/topology
23
+ // to visitors (injectSchema:false is a structural default here, not a flag).
23
24
  //
24
- // `routes` sind app-authored React-ElementeAuth-Screens et al. tragen
25
- // Callback-Props (loggedInHref usw.), die durch keine serialisierbare
26
- // ScreenDefinition passen würden; deshalb laufen sie über diesen Mount und
27
- // nicht über die Registry. Späterer Registry-Content (CMS/Landing) konvergiert
28
- // auf denselben Mount.
25
+ // `routes` are app-authored React elements auth screens et al. carry
26
+ // callback props (loggedInHref etc.) that wouldn't fit through any
27
+ // serializable ScreenDefinition; that's why they run through this mount and
28
+ // not through the registry. Later registry content (CMS/landing) converges
29
+ // on the same mount.
29
30
  //
30
31
  // createPublicSurface({
31
32
  // routes: [{ path: "/login", component: <LoginScreen ... /> }],
@@ -35,28 +36,28 @@ import { type ClientFeatureDefinition, stackWrappers } from "./client-plugin";
35
36
  // });
36
37
 
37
38
  export type PublicRoute = {
38
- /** Exakter window.location.pathname-Match (match-once beim Mount; alle
39
- * Apex-Pages sind Full-Page-Reloads, kein SPA-Router). */
39
+ /** Exact window.location.pathname match (matched once on mount; every
40
+ * apex page is a full-page reload, no SPA router). */
40
41
  readonly path: string;
41
42
  readonly component: ReactNode;
42
43
  };
43
44
 
44
45
  export type CreatePublicSurfaceOptions = {
45
46
  readonly routes: readonly PublicRoute[];
46
- /** Gerendert wenn kein route.path auf den aktuellen Pfad matcht. */
47
+ /** Rendered when no route.path matches the current path. */
47
48
  readonly fallback?: ReactNode;
48
49
  readonly rootId?: string;
49
50
  readonly locale?: LocaleResolver;
50
51
  readonly primitives?: Partial<PrimitivesRegistry>;
51
- /** Dispatcher für Handler-Calls (z.B. anonymer Deletion-Request). Auth-
52
- * Screens brauchen ihn nicht (fetch via auth-client), aber er steht für
53
- * dispatchende Public-Flows bereit. Default: createLiveDispatcher(). */
52
+ /** Dispatcher for handler calls (e.g. an anonymous deletion request).
53
+ * Auth screens don't need it (fetch via auth-client), but it's there for
54
+ * dispatching public flows. Default: createLiveDispatcher(). */
54
55
  readonly dispatcher?: Dispatcher;
55
- /** Feature-Client-ExtensionsNUR `providers` + `translations` werden
56
- * gestackt. `gates` werden bewusst ignoriert: ein AuthGate würde die
57
- * öffentliche Surface hinter Login sperren. */
56
+ /** Feature client extensions ONLY `providers` + `translations` are
57
+ * stacked. `gates` are deliberately ignored: an AuthGate would lock the
58
+ * public surface behind login. */
58
59
  readonly clientFeatures?: readonly ClientFeatureDefinition[];
59
- /** Page-Chrome um den gematchten Content (Apex-/Marketing-Layout). */
60
+ /** Page chrome around the matched content (apex/marketing layout). */
60
61
  readonly shell?: (props: { readonly children: ReactNode }) => ReactNode;
61
62
  };
62
63
 
@@ -94,6 +95,7 @@ export function createPublicSurface(options: CreatePublicSurfaceOptions): { read
94
95
 
95
96
  const tree = (
96
97
  <LocaleProvider resolver={localeResolver} fallbackBundles={fallbackBundles}>
98
+ <DocumentLangSync resolver={localeResolver} />
97
99
  <PrimitivesProvider value={primitives}>
98
100
  <DispatcherProvider dispatcher={dispatcher}>
99
101
  <ToastProvider>{stackWrappers(providers, content)}</ToastProvider>
@@ -0,0 +1,25 @@
1
+ // Keeps document.documentElement.lang in sync with the active locale.
2
+ // Screen readers and the browser's built-in translate offer both read it,
3
+ // and @cosmicdrift/kumiko-dispatcher-live reads it back to tag outgoing
4
+ // requests with X-Locale (see dispatcher-live/src/locale.ts). This is the
5
+ // only way the dispatcher — a separate, server-dep-free package with no
6
+ // access to the resolver — learns the active language.
7
+ //
8
+ // Lives here, not in @cosmicdrift/kumiko-renderer: that package is DOM-free
9
+ // by design (Renderer-Boundaries Guard enforces it — web/native both
10
+ // consume it). createApp/createPublicSurface are the two places every web
11
+ // app already goes through, so mounting this here covers every app with
12
+ // zero app-side wiring, same as createBrowserLocaleResolver.
13
+
14
+ import type { LocaleResolver } from "@cosmicdrift/kumiko-headless";
15
+ import { useEffect } from "react";
16
+
17
+ export function DocumentLangSync({ resolver }: { readonly resolver: LocaleResolver }): null {
18
+ useEffect(() => {
19
+ document.documentElement.lang = resolver.locale();
20
+ return resolver.subscribe(() => {
21
+ document.documentElement.lang = resolver.locale();
22
+ });
23
+ }, [resolver]);
24
+ return null;
25
+ }
@@ -64,10 +64,12 @@ import {
64
64
  Receipt,
65
65
  Rocket,
66
66
  Search,
67
+ Send,
67
68
  Server,
68
69
  Settings,
69
70
  Share2,
70
71
  Shield,
72
+ ShieldCheck,
71
73
  Sparkles,
72
74
  Table,
73
75
  Tag,
@@ -147,6 +149,8 @@ const NAV_ICONS = {
147
149
  home: Home,
148
150
  bell: Bell,
149
151
  shield: Shield,
152
+ "shield-check": ShieldCheck,
153
+ send: Send,
150
154
  settings: Settings,
151
155
  users: Users,
152
156
  user: User,