@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.
|
|
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.
|
|
20
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
21
|
-
"@cosmicdrift/kumiko-renderer": "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.
|
|
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",
|
package/src/app/create-app.tsx
CHANGED
|
@@ -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
|
|
18
|
-
//
|
|
19
|
-
// feature-providers)
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
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`
|
|
25
|
-
//
|
|
26
|
-
// ScreenDefinition
|
|
27
|
-
//
|
|
28
|
-
//
|
|
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
|
-
/**
|
|
39
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
52
|
-
*
|
|
53
|
-
*
|
|
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
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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
|
|
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
|
+
}
|
package/src/layout/nav-tree.tsx
CHANGED
|
@@ -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,
|