@cosmicdrift/kumiko-renderer 0.14.0 → 0.16.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",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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>",
|
|
@@ -42,4 +42,4 @@
|
|
|
42
42
|
"README.md",
|
|
43
43
|
"LICENSE"
|
|
44
44
|
]
|
|
45
|
-
}
|
|
45
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { LocaleResolver } from "@cosmicdrift/kumiko-headless";
|
|
3
3
|
import { act, render, renderHook } from "@testing-library/react";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
|
-
import { describe, expect, test } from "vitest";
|
|
6
5
|
import {
|
|
7
6
|
createStaticLocaleResolver,
|
|
8
7
|
LocaleProvider,
|
|
@@ -32,7 +31,6 @@ function makeStatefulResolver(initial: string): LocaleResolver {
|
|
|
32
31
|
},
|
|
33
32
|
};
|
|
34
33
|
}
|
|
35
|
-
|
|
36
34
|
const wrap =
|
|
37
35
|
(resolver: LocaleResolver, fallbackBundles?: TranslationsByLocale[]) =>
|
|
38
36
|
({ children }: { readonly children: ReactNode }): ReactNode => (
|
|
@@ -40,7 +38,6 @@ const wrap =
|
|
|
40
38
|
{children}
|
|
41
39
|
</LocaleProvider>
|
|
42
40
|
);
|
|
43
|
-
|
|
44
41
|
describe("useTranslation — lookup order", () => {
|
|
45
42
|
test("App-Resolver wins when it returns a non-key value", () => {
|
|
46
43
|
const resolver: LocaleResolver = {
|
|
@@ -50,7 +47,6 @@ describe("useTranslation — lookup order", () => {
|
|
|
50
47
|
const { result } = renderHook(() => useTranslation(), { wrapper: wrap(resolver) });
|
|
51
48
|
expect(result.current("hello")).toBe("Resolved by app");
|
|
52
49
|
});
|
|
53
|
-
|
|
54
50
|
test("falls back to plugin-bundle for current locale", () => {
|
|
55
51
|
const resolver = createStaticLocaleResolver({ locale: "de" });
|
|
56
52
|
const bundles: TranslationsByLocale[] = [{ de: { greet: "Hallo" }, en: { greet: "Hello" } }];
|
|
@@ -59,7 +55,6 @@ describe("useTranslation — lookup order", () => {
|
|
|
59
55
|
});
|
|
60
56
|
expect(result.current("greet")).toBe("Hallo");
|
|
61
57
|
});
|
|
62
|
-
|
|
63
58
|
test("strips region for bundle lookup (de-AT → de)", () => {
|
|
64
59
|
const resolver = createStaticLocaleResolver({ locale: "de-AT" });
|
|
65
60
|
const bundles: TranslationsByLocale[] = [{ de: { greet: "Hallo" } }];
|
|
@@ -68,7 +63,6 @@ describe("useTranslation — lookup order", () => {
|
|
|
68
63
|
});
|
|
69
64
|
expect(result.current("greet")).toBe("Hallo");
|
|
70
65
|
});
|
|
71
|
-
|
|
72
66
|
test("falls back to fallbackLocale (en) when current locale missing", () => {
|
|
73
67
|
const resolver = createStaticLocaleResolver({ locale: "fr" });
|
|
74
68
|
const bundles: TranslationsByLocale[] = [{ de: { greet: "Hallo" }, en: { greet: "Hello" } }];
|
|
@@ -77,13 +71,11 @@ describe("useTranslation — lookup order", () => {
|
|
|
77
71
|
});
|
|
78
72
|
expect(result.current("greet")).toBe("Hello");
|
|
79
73
|
});
|
|
80
|
-
|
|
81
74
|
test("returns key as-is when nothing resolves", () => {
|
|
82
75
|
const resolver = createStaticLocaleResolver({ locale: "de" });
|
|
83
76
|
const { result } = renderHook(() => useTranslation(), { wrapper: wrap(resolver) });
|
|
84
77
|
expect(result.current("missing.key")).toBe("missing.key");
|
|
85
78
|
});
|
|
86
|
-
|
|
87
79
|
test("interpolates {param}-placeholders from params arg", () => {
|
|
88
80
|
const resolver = createStaticLocaleResolver({ locale: "de" });
|
|
89
81
|
const bundles: TranslationsByLocale[] = [{ de: { greet: "Hallo {name}!" } }];
|
|
@@ -93,31 +85,26 @@ describe("useTranslation — lookup order", () => {
|
|
|
93
85
|
expect(result.current("greet", { name: "Marc" })).toBe("Hallo Marc!");
|
|
94
86
|
});
|
|
95
87
|
});
|
|
96
|
-
|
|
97
88
|
describe("useTranslation — re-render on locale change", () => {
|
|
98
89
|
test("subscribe fires when setLocale runs, hook returns new value", () => {
|
|
99
90
|
const resolver = makeStatefulResolver("de");
|
|
100
91
|
const bundles: TranslationsByLocale[] = [{ de: { greet: "Hallo" }, en: { greet: "Hello" } }];
|
|
101
|
-
|
|
102
92
|
function Probe(): ReactNode {
|
|
103
93
|
const t = useTranslation();
|
|
104
94
|
return <span data-testid="msg">{t("greet")}</span>;
|
|
105
95
|
}
|
|
106
|
-
|
|
107
96
|
const { getByTestId } = render(
|
|
108
97
|
<LocaleProvider resolver={resolver} fallbackBundles={bundles}>
|
|
109
98
|
<Probe />
|
|
110
99
|
</LocaleProvider>,
|
|
111
100
|
);
|
|
112
101
|
expect(getByTestId("msg").textContent).toBe("Hallo");
|
|
113
|
-
|
|
114
102
|
act(() => {
|
|
115
103
|
resolver.setLocale?.("en");
|
|
116
104
|
});
|
|
117
105
|
expect(getByTestId("msg").textContent).toBe("Hello");
|
|
118
106
|
});
|
|
119
107
|
});
|
|
120
|
-
|
|
121
108
|
describe("useLocale", () => {
|
|
122
109
|
test("returns the resolver", () => {
|
|
123
110
|
const resolver = createStaticLocaleResolver({ locale: "de" });
|
package/src/__tests__/qn.test.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
1
|
//
|
|
3
2
|
// useListUrlState pinnt den URL-State-Vertrag pro Screen-ID-Namespace:
|
|
4
3
|
// `?<screenId>.sort=…&<screenId>.dir=…&<screenId>.q=…&<screenId>.page=…`.
|
|
5
4
|
// Zwei Listen auf der Route teilen sich die URL ohne Param-Konflikt.
|
|
6
5
|
// Page wird bei Sort/Filter-Wechsel reseted; Sort bleibt bei Search.
|
|
7
6
|
|
|
7
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
8
8
|
import { act, renderHook } from "@testing-library/react";
|
|
9
9
|
import type { ReactNode } from "react";
|
|
10
|
-
import { describe, expect, test, vi } from "vitest";
|
|
11
10
|
import type { NavApi } from "../app/nav";
|
|
12
11
|
import { NavProvider } from "../app/nav";
|
|
13
12
|
import { useListUrlState } from "../hooks/use-list-url-state";
|
|
@@ -23,8 +22,8 @@ function makeNav(initial: Record<string, string> = {}): NavApi & {
|
|
|
23
22
|
captures: Array<Record<string, string | null>>;
|
|
24
23
|
} = {
|
|
25
24
|
route: undefined,
|
|
26
|
-
navigate:
|
|
27
|
-
replace:
|
|
25
|
+
navigate: mock(),
|
|
26
|
+
replace: mock(),
|
|
28
27
|
hrefFor: () => "",
|
|
29
28
|
get searchParams() {
|
|
30
29
|
return params;
|
|
@@ -41,11 +40,9 @@ function makeNav(initial: Record<string, string> = {}): NavApi & {
|
|
|
41
40
|
};
|
|
42
41
|
return api;
|
|
43
42
|
}
|
|
44
|
-
|
|
45
43
|
function wrapper(nav: NavApi): (props: { children: ReactNode }) => ReactNode {
|
|
46
44
|
return ({ children }) => <NavProvider value={nav}>{children}</NavProvider>;
|
|
47
45
|
}
|
|
48
|
-
|
|
49
46
|
describe("useListUrlState", () => {
|
|
50
47
|
test("Default-State: kein URL-Param → sort=null, q='', page=1", () => {
|
|
51
48
|
const nav = makeNav();
|
|
@@ -54,25 +51,21 @@ describe("useListUrlState", () => {
|
|
|
54
51
|
expect(result.current.q).toBe("");
|
|
55
52
|
expect(result.current.page).toBe(1);
|
|
56
53
|
});
|
|
57
|
-
|
|
58
54
|
test("liest sort+dir aus URL-Params (mit screenId-Prefix)", () => {
|
|
59
55
|
const nav = makeNav({ "orders.sort": "createdAt", "orders.dir": "desc" });
|
|
60
56
|
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
61
57
|
expect(result.current.sort).toEqual({ field: "createdAt", dir: "desc" });
|
|
62
58
|
});
|
|
63
|
-
|
|
64
59
|
test("ignoriert sort einer ANDEREN Liste (Namespacing)", () => {
|
|
65
60
|
const nav = makeNav({ "incidents.sort": "severity", "incidents.dir": "asc" });
|
|
66
61
|
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
67
62
|
expect(result.current.sort).toBeNull();
|
|
68
63
|
});
|
|
69
|
-
|
|
70
64
|
test("invalid dir (z.B. 'foo') → sort=null (defensive parse)", () => {
|
|
71
65
|
const nav = makeNav({ "orders.sort": "name", "orders.dir": "foo" });
|
|
72
66
|
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
73
67
|
expect(result.current.sort).toBeNull();
|
|
74
68
|
});
|
|
75
|
-
|
|
76
69
|
test("setSort schreibt sort+dir, resettet page (atomic update)", () => {
|
|
77
70
|
const nav = makeNav({ "orders.page": "5" });
|
|
78
71
|
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
@@ -86,7 +79,6 @@ describe("useListUrlState", () => {
|
|
|
86
79
|
"orders.page": null, // page-reset bei sort-change
|
|
87
80
|
});
|
|
88
81
|
});
|
|
89
|
-
|
|
90
82
|
test("setSort(null) löscht sort+dir+page", () => {
|
|
91
83
|
const nav = makeNav({
|
|
92
84
|
"orders.sort": "name",
|
|
@@ -103,7 +95,6 @@ describe("useListUrlState", () => {
|
|
|
103
95
|
"orders.page": null,
|
|
104
96
|
});
|
|
105
97
|
});
|
|
106
|
-
|
|
107
98
|
test("setQ schreibt q + resettet page (Sort bleibt unangetastet)", () => {
|
|
108
99
|
const nav = makeNav({
|
|
109
100
|
"orders.sort": "name",
|
|
@@ -122,7 +113,6 @@ describe("useListUrlState", () => {
|
|
|
122
113
|
// die Sortierung nicht.
|
|
123
114
|
expect(nav.captures[0]).not.toHaveProperty("orders.sort");
|
|
124
115
|
});
|
|
125
|
-
|
|
126
116
|
test("setQ('') löscht den q-Key", () => {
|
|
127
117
|
const nav = makeNav({ "orders.q": "acme" });
|
|
128
118
|
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
@@ -134,7 +124,6 @@ describe("useListUrlState", () => {
|
|
|
134
124
|
"orders.page": null,
|
|
135
125
|
});
|
|
136
126
|
});
|
|
137
|
-
|
|
138
127
|
test("setPage(1) löscht den Key (Default-Page = unprefixed URL)", () => {
|
|
139
128
|
const nav = makeNav({ "orders.page": "5" });
|
|
140
129
|
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
@@ -143,7 +132,6 @@ describe("useListUrlState", () => {
|
|
|
143
132
|
});
|
|
144
133
|
expect(nav.captures[0]).toEqual({ "orders.page": null });
|
|
145
134
|
});
|
|
146
|
-
|
|
147
135
|
test("setPage(N>1) speichert die Page als String", () => {
|
|
148
136
|
const nav = makeNav();
|
|
149
137
|
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
@@ -152,7 +140,6 @@ describe("useListUrlState", () => {
|
|
|
152
140
|
});
|
|
153
141
|
expect(nav.captures[0]).toEqual({ "orders.page": "7" });
|
|
154
142
|
});
|
|
155
|
-
|
|
156
143
|
test("invalid page (negativ, NaN, 0) → fallback auf 1", () => {
|
|
157
144
|
const nav = makeNav({ "orders.page": "-3" });
|
|
158
145
|
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { synthesizeActionFormEntity, synthesizeActionFormScreen } from "../action-form-shim";
|
|
3
|
+
|
|
4
|
+
describe("synthesizeActionFormEntity", () => {
|
|
5
|
+
test("wraps inline fields as minimal EntityDefinition", () => {
|
|
6
|
+
const entity = synthesizeActionFormEntity({
|
|
7
|
+
title: { type: "text" },
|
|
8
|
+
});
|
|
9
|
+
expect(entity.fields["title"]).toEqual({ type: "text" });
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("synthesizeActionFormScreen", () => {
|
|
14
|
+
test("maps actionForm screen to entityEdit shape", () => {
|
|
15
|
+
const screen = synthesizeActionFormScreen({
|
|
16
|
+
id: "invite-user",
|
|
17
|
+
type: "actionForm",
|
|
18
|
+
handler: "users:write:invite-user",
|
|
19
|
+
layout: { sections: [{ title: "Invite", fields: ["email"] }] },
|
|
20
|
+
fields: { email: { type: "text" } },
|
|
21
|
+
});
|
|
22
|
+
expect(screen.type).toBe("entityEdit");
|
|
23
|
+
expect(screen.entity).toBe("__action-form__");
|
|
24
|
+
expect(screen.layout).toEqual({ sections: [{ title: "Invite", fields: ["email"] }] });
|
|
25
|
+
});
|
|
26
|
+
});
|
package/CHANGELOG.md
DELETED
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
# @cosmicdrift/kumiko-renderer
|
|
2
|
-
|
|
3
|
-
## 0.14.0
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- @cosmicdrift/kumiko-framework@0.14.0
|
|
8
|
-
- @cosmicdrift/kumiko-headless@0.14.0
|
|
9
|
-
|
|
10
|
-
## 0.13.0
|
|
11
|
-
|
|
12
|
-
### Patch Changes
|
|
13
|
-
|
|
14
|
-
- Updated dependencies [7f56b2f]
|
|
15
|
-
- @cosmicdrift/kumiko-framework@0.13.0
|
|
16
|
-
- @cosmicdrift/kumiko-headless@0.13.0
|
|
17
|
-
|
|
18
|
-
## 0.12.2
|
|
19
|
-
|
|
20
|
-
### Patch Changes
|
|
21
|
-
|
|
22
|
-
- Updated dependencies [597de52]
|
|
23
|
-
- @cosmicdrift/kumiko-framework@0.12.2
|
|
24
|
-
- @cosmicdrift/kumiko-headless@0.12.2
|
|
25
|
-
|
|
26
|
-
## 0.12.1
|
|
27
|
-
|
|
28
|
-
### Patch Changes
|
|
29
|
-
|
|
30
|
-
- Updated dependencies [f2ad7c4]
|
|
31
|
-
- @cosmicdrift/kumiko-framework@0.12.1
|
|
32
|
-
- @cosmicdrift/kumiko-headless@0.12.1
|
|
33
|
-
|
|
34
|
-
## 0.12.0
|
|
35
|
-
|
|
36
|
-
### Patch Changes
|
|
37
|
-
|
|
38
|
-
- @cosmicdrift/kumiko-framework@0.12.0
|
|
39
|
-
- @cosmicdrift/kumiko-headless@0.12.0
|
|
40
|
-
|
|
41
|
-
## 0.11.2
|
|
42
|
-
|
|
43
|
-
### Patch Changes
|
|
44
|
-
|
|
45
|
-
- Updated dependencies [92a84f0]
|
|
46
|
-
- @cosmicdrift/kumiko-framework@0.11.2
|
|
47
|
-
- @cosmicdrift/kumiko-headless@0.11.2
|
|
48
|
-
|
|
49
|
-
## 0.11.1
|
|
50
|
-
|
|
51
|
-
### Patch Changes
|
|
52
|
-
|
|
53
|
-
- @cosmicdrift/kumiko-framework@0.11.1
|
|
54
|
-
- @cosmicdrift/kumiko-headless@0.11.1
|
|
55
|
-
|
|
56
|
-
## 0.11.0
|
|
57
|
-
|
|
58
|
-
### Patch Changes
|
|
59
|
-
|
|
60
|
-
- Updated dependencies [30ea981]
|
|
61
|
-
- Updated dependencies [9347212]
|
|
62
|
-
- @cosmicdrift/kumiko-framework@0.11.0
|
|
63
|
-
- @cosmicdrift/kumiko-headless@0.11.0
|
|
64
|
-
|
|
65
|
-
## 0.10.0
|
|
66
|
-
|
|
67
|
-
### Patch Changes
|
|
68
|
-
|
|
69
|
-
- Updated dependencies [d06f029]
|
|
70
|
-
- Updated dependencies [753d392]
|
|
71
|
-
- @cosmicdrift/kumiko-framework@0.10.0
|
|
72
|
-
- @cosmicdrift/kumiko-headless@0.10.0
|
|
73
|
-
|
|
74
|
-
## 0.9.0
|
|
75
|
-
|
|
76
|
-
### Patch Changes
|
|
77
|
-
|
|
78
|
-
- Updated dependencies [51e22f5]
|
|
79
|
-
- @cosmicdrift/kumiko-framework@0.9.0
|
|
80
|
-
- @cosmicdrift/kumiko-headless@0.9.0
|
|
81
|
-
|
|
82
|
-
## 0.8.1
|
|
83
|
-
|
|
84
|
-
### Patch Changes
|
|
85
|
-
|
|
86
|
-
- Updated dependencies [4b5f91e]
|
|
87
|
-
- @cosmicdrift/kumiko-framework@0.8.1
|
|
88
|
-
- @cosmicdrift/kumiko-headless@0.8.1
|
|
89
|
-
|
|
90
|
-
## 0.8.0
|
|
91
|
-
|
|
92
|
-
### Patch Changes
|
|
93
|
-
|
|
94
|
-
- Updated dependencies [f34af9a]
|
|
95
|
-
- Updated dependencies [dff4123]
|
|
96
|
-
- @cosmicdrift/kumiko-framework@0.8.0
|
|
97
|
-
- @cosmicdrift/kumiko-headless@0.8.0
|
|
98
|
-
|
|
99
|
-
## 0.7.0
|
|
100
|
-
|
|
101
|
-
### Minor Changes
|
|
102
|
-
|
|
103
|
-
- bcf43b6: es-ops: `SeedMembershipRow` exposes `streamTenantId` (stream-tenant aus `kumiko_events.v1`) neben dem payload-`tenantId`. Seed-Authors müssen den `kumiko_events`-JOIN nicht mehr selbst bauen — `m.streamTenantId` ist der korrekte Wert für `systemWriteAs`'s `tenantIdOverride` wenn das Aggregate von einem fremden Executor angelegt wurde (typisches `seedTenantMembership(by=systemAdmin)`-Pattern).
|
|
104
|
-
|
|
105
|
-
### Patch Changes
|
|
106
|
-
|
|
107
|
-
- Updated dependencies [bcf43b6]
|
|
108
|
-
- @cosmicdrift/kumiko-framework@0.7.0
|
|
109
|
-
- @cosmicdrift/kumiko-headless@0.7.0
|
|
110
|
-
|
|
111
|
-
## 0.6.0
|
|
112
|
-
|
|
113
|
-
### Minor Changes
|
|
114
|
-
|
|
115
|
-
- 8489d18: feat(es-ops): Phase 1.5 — tenantIdOverride + dry-run-validator + E2E-Test + Doku
|
|
116
|
-
|
|
117
|
-
Phase 1.5 schließt die Lücken aus Phase 1 die den ersten Driver-Use-Case
|
|
118
|
-
(publicstatus admin-roles) blockten. Siehe Retro:
|
|
119
|
-
`kumiko-platform/docs/plans/features/es-ops-phase1-retro.md` (PR #9).
|
|
120
|
-
|
|
121
|
-
**A1 — tenantIdOverride:**
|
|
122
|
-
`SeedMigrationContext.systemWriteAs(qn, payload, tenantIdOverride?)`.
|
|
123
|
-
Default SYSTEM_TENANT_ID (unverändert für System-scope-Aggregates wie
|
|
124
|
-
config-values). Mit override: `createSystemUser(tenantIdOverride)` als
|
|
125
|
-
Executor, damit der Event-Store-Executor den Aggregate-Stream im
|
|
126
|
-
richtigen Tenant findet. Fix für die `version_conflict`-Klasse-Bug
|
|
127
|
-
(Memory `feedback_event_store_tenant_consistency.md`).
|
|
128
|
-
|
|
129
|
-
**A2 — dry-run-validator:**
|
|
130
|
-
Runner parsed seed-files vor `migration.run()` per regex
|
|
131
|
-
`systemWriteAs\(["']([^"']+)["']`, sammelt handler-QNs, validiert
|
|
132
|
-
gegen `registry.getWriteHandler(qn)`. Fail-fast mit klarer Message
|
|
133
|
-
|
|
134
|
-
- Datei + QN statt zur Runtime "handler not found". Catched camelCase-
|
|
135
|
-
typos (kebab-case-vs-camelCase Drift) + andere QN-Drift zur Boot-Zeit.
|
|
136
|
-
runProdApp reicht den richtigen Registry rein (`registry` neu in
|
|
137
|
-
RunPendingSeedMigrationsArgs).
|
|
138
|
-
|
|
139
|
-
**A3 — E2E-Test:**
|
|
140
|
-
`packages/bundled-features/src/__tests__/es-ops-e2e.integration.ts`
|
|
141
|
-
mit `setupTestStack`-Pattern: tenant+config Features echt geladen,
|
|
142
|
-
echtes Membership-Aggregate via TenantHandlers.addMember im Demo-Tenant,
|
|
143
|
-
seed-migration ruft update-member-roles mit tenantIdOverride → write
|
|
144
|
-
geht durch, Marker landed, Event in Store, Read-Model aktualisiert.
|
|
145
|
-
Plus typo-Test: seed mit camelCase fail-t Dry-Run mit
|
|
146
|
-
`/dry-run found.*unknown handler-QN/`. **TDD-First**: ohne A1+A2 wäre
|
|
147
|
-
der test rot.
|
|
148
|
-
|
|
149
|
-
**A4 — Doku:**
|
|
150
|
-
`framework/src/es-ops/README.md` erweitert um „Wann brauche ich
|
|
151
|
-
tenantIdOverride?" + „Deployment-Anforderungen" (Docker COPY, Idempotenz,
|
|
152
|
-
Multi-Replica) + „Lokaler Smoke vor Push". Recipe-README + seed-files
|
|
153
|
-
auf neue API aktualisiert.
|
|
154
|
-
|
|
155
|
-
**A5 — Smoke-Skript-Template:**
|
|
156
|
-
`samples/recipes/seed-migration/scripts/smoke.ts` als copy-paste-Template
|
|
157
|
-
für App-Authors: Bun-runnable, offline (read-only, kein DB-Write),
|
|
158
|
-
validiert Module-Load + QN-Resolution + System-User-Access. Recipe-
|
|
159
|
-
README dokumentiert Pflicht-Pattern.
|
|
160
|
-
|
|
161
|
-
**Bonus-Fix:**
|
|
162
|
-
`tenant:write:create`-access auf `["system", "SystemAdmin"]` erweitert
|
|
163
|
-
(symmetrisch zu update-member-roles). Aufgedeckt durch Recipe-Smoke +
|
|
164
|
-
initial-tenants-Seed. Pinning-Test in `tenant.integration.ts` updated.
|
|
165
|
-
|
|
166
|
-
**Test-State:** 45/45 grün (Pre-Push). Typecheck clean. Biome clean.
|
|
167
|
-
as-cast-Audit clean. Guard-silent-skip clean. Recipe-Smoke clean.
|
|
168
|
-
|
|
169
|
-
**Folge-Step (separater PR):** publicstatus driver-sample reaktivieren
|
|
170
|
-
mit lokalem Pre-Push-Smoke gegen publicstatus' echtes Feature-Set.
|
|
171
|
-
|
|
172
|
-
### Patch Changes
|
|
173
|
-
|
|
174
|
-
- Updated dependencies [8489d18]
|
|
175
|
-
- @cosmicdrift/kumiko-framework@0.6.0
|
|
176
|
-
- @cosmicdrift/kumiko-headless@0.6.0
|
|
177
|
-
|
|
178
|
-
## 0.5.2
|
|
179
|
-
|
|
180
|
-
### Patch Changes
|
|
181
|
-
|
|
182
|
-
- 4f0d781: fix(tenant): updateMemberRoles erlaubt "system"-Rolle (symmetrisch zu create)
|
|
183
|
-
|
|
184
|
-
Drift innerhalb des tenant-Features: `tenant:write:create` akzeptierte
|
|
185
|
-
`["system", "SystemAdmin"]`, `tenant:write:update-member-roles` aber
|
|
186
|
-
nur `["SystemAdmin"]`. Konsequenz: ops-tooling und seed-migrations
|
|
187
|
-
(`createSystemUser` mit `roles: ["system"]`) konnten den Handler nicht
|
|
188
|
-
aufrufen — `access_denied`.
|
|
189
|
-
|
|
190
|
-
Live entdeckt beim ersten Driver-Sample der es-ops Phase 1: publicstatus
|
|
191
|
-
seed `2026-05-20-fix-admin-roles.ts` rief `update-member-roles` via
|
|
192
|
-
`systemWriteAs` → access_denied → Pod CrashLoopBackOff.
|
|
193
|
-
|
|
194
|
-
Plus access-rule-Pinning-Test in `tenant.integration.ts`-scenario-7.
|
|
195
|
-
|
|
196
|
-
- Updated dependencies [4f0d781]
|
|
197
|
-
- @cosmicdrift/kumiko-framework@0.5.2
|
|
198
|
-
- @cosmicdrift/kumiko-headless@0.5.2
|
|
199
|
-
|
|
200
|
-
## 0.5.1
|
|
201
|
-
|
|
202
|
-
### Patch Changes
|
|
203
|
-
|
|
204
|
-
- 0e00015: fix(es-ops): path.resolve statt path.join für seedsDir → seed-files
|
|
205
|
-
|
|
206
|
-
Bun's `await import()` braucht absolute Pfade. Wenn der App-Author
|
|
207
|
-
`runProdApp({ seedsDir: "./seeds" })` setzt (relativ), würde
|
|
208
|
-
`path.join("./seeds", "foo.ts")` einen relativen Pfad liefern → Bun's
|
|
209
|
-
Import-Resolver such relativ zum `runner.ts`-Modul (nicht zum
|
|
210
|
-
`process.cwd()`) → `Cannot find module 'seeds/...' from '<runner-path>'`.
|
|
211
|
-
|
|
212
|
-
`path.resolve` löst gegen `process.cwd()` auf → absolute Pfade →
|
|
213
|
-
Import funktioniert. Aufgedeckt beim ersten Live-Boot der publicstatus-
|
|
214
|
-
Driver-Migration (Pod CrashLoopBackOff).
|
|
215
|
-
|
|
216
|
-
- Updated dependencies [0e00015]
|
|
217
|
-
- @cosmicdrift/kumiko-framework@0.5.1
|
|
218
|
-
- @cosmicdrift/kumiko-headless@0.5.1
|
|
219
|
-
|
|
220
|
-
## 0.5.0
|
|
221
|
-
|
|
222
|
-
### Minor Changes
|
|
223
|
-
|
|
224
|
-
- 7ff69ab: feat(es-ops): Phase 1 — file-based seed-migrations
|
|
225
|
-
|
|
226
|
-
Neues first-class Operations-Pattern fürs Framework. Liefert `seed-migrations`
|
|
227
|
-
als drizzle-migrate-equivalent für Event-Sourcing-Aggregate-Updates die
|
|
228
|
-
idempotent-Seeder nicht erfassen können (z.B. „Member hat schon eine
|
|
229
|
-
Rolle, aber jetzt soll noch eine dazukommen").
|
|
230
|
-
|
|
231
|
-
Public-API:
|
|
232
|
-
|
|
233
|
-
- `runProdApp({ seedsDir })` — Auto-apply pending Migrations beim Boot
|
|
234
|
-
- `SeedMigration`-Interface (default-Export einer `seeds/<id>.ts`-File)
|
|
235
|
-
- `SeedMigrationContext` mit `systemWriteAs` (ruft existing write-handler
|
|
236
|
-
als System-User) + Read-Helpers (`findUserByEmail`,
|
|
237
|
-
`findMembershipsOfUser`, `findTenants`)
|
|
238
|
-
- CLI: `bunx kumiko ops seed:new|status|apply`
|
|
239
|
-
- Tracking-Table `kumiko_es_operations` mit `operation_type`-Discriminator
|
|
240
|
-
(vorbereitet auf Phase 2+ Operations: projection-rebuild, event-replay,
|
|
241
|
-
stream-migration, ...)
|
|
242
|
-
- Env-Flags: `KUMIKO_SKIP_ES_OPS=1` (alle skippen für Recovery),
|
|
243
|
-
`KUMIKO_SKIP_ES_OPS_<ID>=1` (einzelne kaputte skippen)
|
|
244
|
-
|
|
245
|
-
Garantien: single-run via tracking, atomic via per-migration-Tx,
|
|
246
|
-
chronological order via filename-prefix, fail-stop bei Failure (kein
|
|
247
|
-
Partial-Apply), ES-konform via Handler-Dispatch.
|
|
248
|
-
|
|
249
|
-
Sub-path-Export: `@cosmicdrift/kumiko-framework/es-ops`
|
|
250
|
-
|
|
251
|
-
Plan-Doc: `kumiko-platform/docs/plans/features/es-ops.md`
|
|
252
|
-
Recipe: `samples/recipes/seed-migration/`
|
|
253
|
-
Driver-Use-Case: publicstatus admin-roles-drift (parallel-Branch
|
|
254
|
-
`feat/es-ops-driver-admin-roles`).
|
|
255
|
-
|
|
256
|
-
Phase 2+ skizziert + offen markiert — Implementation pro Use-Case.
|
|
257
|
-
|
|
258
|
-
### Patch Changes
|
|
259
|
-
|
|
260
|
-
- Updated dependencies [7ff69ab]
|
|
261
|
-
- @cosmicdrift/kumiko-framework@0.5.0
|
|
262
|
-
- @cosmicdrift/kumiko-headless@0.5.0
|
|
263
|
-
|
|
264
|
-
## 0.4.1
|
|
265
|
-
|
|
266
|
-
### Patch Changes
|
|
267
|
-
|
|
268
|
-
- 010b410: feat(auth-email-password): "Bestätigungs-Mail erneut senden" im LoginScreen
|
|
269
|
-
|
|
270
|
-
LoginScreen bietet bei reason=email_not_verified jetzt einen Resend-Link
|
|
271
|
-
im Fehler-Banner — der existierende `requestEmailVerification`-Endpoint
|
|
272
|
-
wird direkt aufgerufen, der Banner wechselt nach Erfolg zum Info-Variant
|
|
273
|
-
("Wir haben dir eine neue Bestätigungs-Mail geschickt.").
|
|
274
|
-
|
|
275
|
-
UX-Details:
|
|
276
|
-
|
|
277
|
-
- Bei 429 → inline-Hint "Bitte warte kurz und versuche es erneut."
|
|
278
|
-
- Bei Netzwerk/sonstigen Fehlern → inline-Hint "Konnte nicht senden."
|
|
279
|
-
- Anti-Typo-Gate: ändert der User die Email-Eingabe nach dem Login-Fail,
|
|
280
|
-
verschwindet der Resend-Link — sonst würde Resend silent-success an die
|
|
281
|
-
geänderte (potentiell typoed) Adresse gehen ohne User-Feedback.
|
|
282
|
-
- Andere Failure-Codes (invalid_credentials etc.) zeigen weiterhin keinen
|
|
283
|
-
Resend-Link.
|
|
284
|
-
|
|
285
|
-
i18n: 4 neue Keys (DE+EN) im `auth.login.resend*`-Namespace, additive.
|
|
286
|
-
Apps die ihre Translations override-en müssen nichts ändern.
|
|
287
|
-
|
|
288
|
-
Additive UI-Feature — keine API-Breaks, keine Schema-Migration.
|
|
289
|
-
|
|
290
|
-
- Updated dependencies [010b410]
|
|
291
|
-
- @cosmicdrift/kumiko-framework@0.4.1
|
|
292
|
-
- @cosmicdrift/kumiko-headless@0.4.1
|
|
293
|
-
|
|
294
|
-
## 0.4.0
|
|
295
|
-
|
|
296
|
-
### Minor Changes
|
|
297
|
-
|
|
298
|
-
- 825e7d2: Visual-Tree V.1.4 → V.1.6 — Feature-complete Editor + Folder-Hierarchy + Roving-tabindex.
|
|
299
|
-
|
|
300
|
-
**V.1.4** — explicit `folder?: string` Schema-Field auf text-block-entity. Slug bleibt
|
|
301
|
-
kebab-only validiert, Folder explizit gesetzt. Tree gruppiert via `groupBlocksByFolder`
|
|
302
|
-
(ersetzt `groupBlocksBySlugPrefix`). `Subscribe<T>` Signature um optional `emitError`
|
|
303
|
-
erweitert für explicit async-error-Pfade. ProviderBranch zeigt Error-Banner mit
|
|
304
|
-
Retry-Button. Drift-Test pinnt seedTextBlock-vs-set.write Slug-Validation.
|
|
305
|
-
|
|
306
|
-
**V.1.4b** — URL-State-Routing für Editor-Target via `nav.searchParams`. F5 + Back-Button
|
|
307
|
-
stellen den Editor-State wieder her. Format: `?t=text-content:edit&a_slug=...&a_lang=...`.
|
|
308
|
-
Plus `useDispatchTarget` hook ersetzt globalen `dispatchTarget` als empfohlenen Production-
|
|
309
|
-
Pfad (legacy bleibt für Test-Hooks).
|
|
310
|
-
|
|
311
|
-
**V.1.5** — Arrow-Key-Navigation (`<aside role="tree">`, ARIA-tree-Pattern) + SSE-driven
|
|
312
|
-
Tree-Refresh. `ClientFeatureDefinition.treeEntities?: string[]` listet Entity-Namen pro
|
|
313
|
-
Provider; live-events triggern provider-re-mount → Stale-Tree-state="stub"→"filled"
|
|
314
|
-
flippt nach save automatisch.
|
|
315
|
-
|
|
316
|
-
**V.1.5c+d** — Active-Node-Highlight (explicit blue + 2px border-l + scrollIntoView),
|
|
317
|
-
VS-Code-Polish (compact spacing, focus-visible, folder-icon-color text-amber, indent-
|
|
318
|
-
guides per ancestor-depth), Folder-Wrapper für legal-pages ("📁 Legal" + slug-first
|
|
319
|
-
Verschachtelung) und text-content ("📁 Content").
|
|
320
|
-
|
|
321
|
-
**V.1.6** — Multi-level Folder-Splitting (`folder="page/marketing"` → nested folders,
|
|
322
|
-
walk-or-create-pattern, folder/leaf-collision-tolerant). Roving-tabindex (nur focused-
|
|
323
|
-
treeitem hat tabIndex=0, Tab cyclt aus dem Tree raus).
|
|
324
|
-
|
|
325
|
-
35/35 kumiko check PASS, 13/13 group-blocks + 22/22 text-content integration tests grün.
|
|
326
|
-
Browser + Keyboard lokal validated.
|
|
327
|
-
|
|
328
|
-
**Breaking**: `TreeContext` Type entfernt (V.1.2 SR2-Rip — war nie genutzt). Provider sind
|
|
329
|
-
session-bound: `TreeChildrenSubscribe = () => Subscribe<T>` statt `(ctx) => Subscribe<T>`.
|
|
330
|
-
|
|
331
|
-
**V.1.7-Followups**: useEffect-deps in VisualTree-focus-init (Performance), Cancellation-
|
|
332
|
-
Token in TreeProvider's fetch (emit-after-unmount-warning), inline-rename, drag-drop,
|
|
333
|
-
file-icons per slug-extension, parent-jump bei ArrowLeft auf collapsed-item.
|
|
334
|
-
|
|
335
|
-
### Patch Changes
|
|
336
|
-
|
|
337
|
-
- Updated dependencies [825e7d2]
|
|
338
|
-
- @cosmicdrift/kumiko-framework@0.4.0
|
|
339
|
-
- @cosmicdrift/kumiko-headless@0.4.0
|
|
340
|
-
|
|
341
|
-
## 0.3.0
|
|
342
|
-
|
|
343
|
-
### Minor Changes
|
|
344
|
-
|
|
345
|
-
- 0.3.0 bringt zwei neue Subsysteme (Step-Engine Tier-3 + Visual-Tree) plus
|
|
346
|
-
eine AST-Codemod-Pipeline als Vorarbeit für den L2-AI-Layer.
|
|
347
|
-
|
|
348
|
-
### Breaking Changes
|
|
349
|
-
|
|
350
|
-
- `skipTransitionGuard` → `unsafeSkipTransitionGuard` (Rename in
|
|
351
|
-
feature-ast + engine). Der `unsafe`-Prefix macht die Tragweite des
|
|
352
|
-
Casts sichtbar und ist konsistent zur `unsafeProjectionUpsert`- und
|
|
353
|
-
`r.rawTable`-Konvention. Migration: 1:1-Ersetzung, keine Verhaltens-Änderung.
|
|
354
|
-
|
|
355
|
-
### Features
|
|
356
|
-
|
|
357
|
-
- **Step-Engine M.4 — Tier-3 Workflow-Engine.** Neue Step-Vocabulary
|
|
358
|
-
`wait`, `waitForEvent`, `retry` ermöglicht persistierte Long-Running-Flows
|
|
359
|
-
über Job-Boundaries hinweg. Q7 Snapshot-at-Start hängt jedem Step-Run
|
|
360
|
-
einen SHA-256-Fingerprint des Aggregat-Zustands an, sodass Replays
|
|
361
|
-
deterministisch gegen den ursprünglichen Eingangszustand laufen.
|
|
362
|
-
- **Visual-Tree V.1.x — Tree-API + Editor-Panel.** Neue `VisualTree`-
|
|
363
|
-
Component plus TreeProvider-Pattern; erste TreeProviders für
|
|
364
|
-
`text-content` und `legal-pages` (CMS-light + Impressum/Privacy).
|
|
365
|
-
Fundament für den späteren No-Code-Designer (~3000 LOC, 98 Tests).
|
|
366
|
-
- **Codemod-Pipeline.** AST-basierte Patcher-Module für strukturelle
|
|
367
|
-
Feature-Edits — wird vom kommenden L2-AI-Layer als Tool-Surface
|
|
368
|
-
verwendet, ist aber eigenständig nutzbar für ts-morph-style Migrationen.
|
|
369
|
-
- **user-data-rights Sample-Recipe.** DSGVO Art. 15/17/18/20 vollständig
|
|
370
|
-
als Sample-Recipe (`samples/recipes/`) inklusive README — zeigt die
|
|
371
|
-
Export- und Forget-Pipeline gegen den `compliance-profiles`-Default
|
|
372
|
-
(`eu-dsgvo`).
|
|
373
|
-
|
|
374
|
-
### Fixes
|
|
375
|
-
|
|
376
|
-
- `tier-engine`: auto-default-tier-Hook benutzt jetzt `ctx.db.raw` für
|
|
377
|
-
Event-Store-Operationen (#37, vorher: stiller Bug, 22 Tage live).
|
|
378
|
-
- `engine`: unsafe-projection-upsert nutzt `as never` statt `as any` —
|
|
379
|
-
schmaler Cast-Surface, weniger Compiler-Knebel.
|
|
380
|
-
- `visual-tree`: runtime-isolation marker für client-konsumierte Files,
|
|
381
|
-
damit der Multi-Entry-Build den richtigen Bundle-Split bekommt.
|
|
382
|
-
- `feature-ast`: vollständiger `unsafeSkipTransitionGuard`-Rename (war
|
|
383
|
-
in zwei Modulen noch der alte Name).
|
|
384
|
-
- `framework`: Error-Reasons + `noConsole`-Lint + No-Date-API-Guard
|
|
385
|
-
wieder push-ready.
|
|
386
|
-
|
|
387
|
-
### Library-Updates
|
|
388
|
-
|
|
389
|
-
hono 4.12, jose 6.2, stripe 22.1, meilisearch 0.58, marked 18,
|
|
390
|
-
bun-types 1.3.13, lucide-react 1.14, bullmq 5.76, ioredis 5.10,
|
|
391
|
-
i18next 26.0, react + radix-ui-primitives auf aktuelle Minors.
|
|
392
|
-
|
|
393
|
-
### Patch Changes
|
|
394
|
-
|
|
395
|
-
- Updated dependencies
|
|
396
|
-
- @cosmicdrift/kumiko-framework@0.3.0
|
|
397
|
-
- @cosmicdrift/kumiko-headless@0.3.0
|
|
398
|
-
|
|
399
|
-
## 0.2.3
|
|
400
|
-
|
|
401
|
-
### Patch Changes
|
|
402
|
-
|
|
403
|
-
- @cosmicdrift/kumiko-framework@0.2.3
|
|
404
|
-
- @cosmicdrift/kumiko-headless@0.2.3
|
|
405
|
-
|
|
406
|
-
## 0.2.2
|
|
407
|
-
|
|
408
|
-
### Patch Changes
|
|
409
|
-
|
|
410
|
-
- 7a7da3e: Re-publish 0.2.1 → 0.2.2 mit korrekt aufgelösten cross-package-Versionen.
|
|
411
|
-
0.2.1 hatte `workspace:*` als Wert in den dependencies (npm publish ohne
|
|
412
|
-
yarn-pack rewrite), Konsumenten bekamen "Workspace not found".
|
|
413
|
-
|
|
414
|
-
publish-with-oidc.sh nutzt jetzt `yarn pack` (rewrited workspace:\*) +
|
|
415
|
-
`npm publish <tarball>` (OIDC + provenance).
|
|
416
|
-
|
|
417
|
-
- Updated dependencies [7a7da3e]
|
|
418
|
-
- @cosmicdrift/kumiko-framework@0.2.2
|
|
419
|
-
- @cosmicdrift/kumiko-headless@0.2.2
|
|
420
|
-
|
|
421
|
-
## 0.2.1
|
|
422
|
-
|
|
423
|
-
### Patch Changes
|
|
424
|
-
|
|
425
|
-
- 48b7f6a: CI: switch publish to npm-CLI with OIDC Trusted Publishing + provenance.
|
|
426
|
-
No source changes — verifies the new publish path produces a verified-
|
|
427
|
-
provenance attestation on npmjs.com instead of token-based publish.
|
|
428
|
-
- Updated dependencies [48b7f6a]
|
|
429
|
-
- @cosmicdrift/kumiko-framework@0.2.1
|
|
430
|
-
- @cosmicdrift/kumiko-headless@0.2.1
|
|
431
|
-
|
|
432
|
-
## 0.2.0
|
|
433
|
-
|
|
434
|
-
### Minor Changes
|
|
435
|
-
|
|
436
|
-
- 6c70b6f: fix(tenant): seedTenant idempotent gegen Event-Store-Projection-Drift.
|
|
437
|
-
|
|
438
|
-
Verhindert version_conflict beim App-Boot wenn Aggregat existiert aber
|
|
439
|
-
Projection-Row fehlt (rebuild-drift, async-lag, manueller DB-Eingriff).
|
|
440
|
-
|
|
441
|
-
### Patch Changes
|
|
442
|
-
|
|
443
|
-
- Updated dependencies [6c70b6f]
|
|
444
|
-
- @cosmicdrift/kumiko-framework@0.2.0
|
|
445
|
-
- @cosmicdrift/kumiko-headless@0.2.0
|
|
446
|
-
|
|
447
|
-
## 0.1.0
|
|
448
|
-
|
|
449
|
-
### Minor Changes
|
|
450
|
-
|
|
451
|
-
- 59ba6d7: Initial public release of Kumiko — AI-native backend builder.
|
|
452
|
-
|
|
453
|
-
What ships in 0.1.0:
|
|
454
|
-
|
|
455
|
-
- **Engine** (`@cosmicdrift/kumiko-framework`): `defineFeature`, `r.entity`, `r.writeHandler`, `r.queryHandler`, `r.projection`, `r.multiStreamProjection`, `r.hook`, `r.translations`, `r.crud`, `r.referenceData`, `r.screen`, `r.nav`, `r.authClaims`, full lifecycle pipeline with field-level access checks
|
|
456
|
-
- **Pipeline** (`@cosmicdrift/kumiko-framework`): `createDispatcher`, JWT auth via jose, Zod schema validation, role-based access checks, command/write/query split
|
|
457
|
-
- **DB** (`@cosmicdrift/kumiko-framework`): Drizzle helpers (`buildDrizzleTable`, `applyCursorQuery`), CRUD executor, Postgres dialect, optimistic locking, soft delete, multi-tenant scoping
|
|
458
|
-
- **Event sourcing** (`@cosmicdrift/kumiko-framework`): aggregate streams, single + multi-stream projections, event upcasters, asOf queries, archive support, AsyncDaemon-pattern dispatcher
|
|
459
|
-
- **Bundled features** (`@cosmicdrift/kumiko-bundled-features`): auth-email-password, sessions, tenants, users, jobs, secrets, file-provider-s3, mail-transport-smtp/inmemory, billing-foundation, cap-counter, channel-in-app, delivery, feature-toggles, legal-pages
|
|
460
|
-
- **Renderer** (`@cosmicdrift/kumiko-renderer`, `@cosmicdrift/kumiko-renderer-web`): schema-driven CRUD UI for React + Expo Web, override paths, list debounce, theme tokens
|
|
461
|
-
- **Headless** (`@cosmicdrift/kumiko-headless`): view-models for list/edit screens, locale-aware
|
|
462
|
-
- **Dev server** (`@cosmicdrift/kumiko-dev-server`): `runDevApp`, `runProdApp`, `kumiko-build` for production bundles (client + server), Docker-ready
|
|
463
|
-
- **Realtime** (`@cosmicdrift/kumiko-dispatcher-live`): SSE broadcast across tenants, Redis Pub/Sub backend
|
|
464
|
-
- **CLI** (`bin/kumiko.ts`): interactive dev menu, test runners, check pipeline (Biome + TypeScript + 18 guards + Vitest)
|
|
465
|
-
|
|
466
|
-
This is a pre-1.0 release — APIs may change between minor versions. Breaking changes will be documented per release.
|
|
467
|
-
|
|
468
|
-
### Patch Changes
|
|
469
|
-
|
|
470
|
-
- Updated dependencies [59ba6d7]
|
|
471
|
-
- @cosmicdrift/kumiko-framework@0.1.0
|
|
472
|
-
- @cosmicdrift/kumiko-headless@0.1.0
|