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