@cosmicdrift/kumiko-samples 0.351.0 → 0.353.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.
Files changed (114) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +2 -2
  3. package/packages/bundled-features/src/agent-tools/agent-manifest.ts +15 -2
  4. package/packages/bundled-features/src/agent-tools/changes.json +12 -0
  5. package/packages/bundled-features/src/agent-tools/manifest-deny-qns.ts +25 -0
  6. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +4 -1
  7. package/packages/bundled-features/src/audit/changes.json +6 -0
  8. package/packages/bundled-features/src/audit/handlers/list.query.ts +20 -10
  9. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +2 -3
  10. package/packages/bundled-features/src/auth-mfa/screens.ts +2 -2
  11. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +3 -4
  12. package/packages/bundled-features/src/billing-foundation/checkout-core.ts +14 -2
  13. package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +7 -1
  14. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +95 -54
  15. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +247 -0
  16. package/packages/bundled-features/src/cap-counter/changes.json +28 -0
  17. package/packages/bundled-features/src/cap-counter/constants.ts +8 -0
  18. package/packages/bundled-features/src/cap-counter/feature.ts +7 -0
  19. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +5 -0
  20. package/packages/bundled-features/src/cap-counter/index.ts +9 -0
  21. package/packages/bundled-features/src/cap-counter/payload-field.ts +10 -0
  22. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +51 -0
  23. package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +3 -0
  24. package/packages/bundled-features/src/cap-counter/tables.ts +53 -0
  25. package/packages/bundled-features/src/cap-counter/value-cap-guard.ts +70 -0
  26. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +63 -70
  27. package/packages/bundled-features/src/cap-overview/access.ts +2 -9
  28. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +5 -2
  29. package/packages/bundled-features/src/cap-overview/handlers/tenant-options.query.ts +6 -9
  30. package/packages/bundled-features/src/channel-discord/feature.ts +1 -0
  31. package/packages/bundled-features/src/channel-slack/feature.ts +1 -0
  32. package/packages/bundled-features/src/channel-teams/feature.ts +1 -0
  33. package/packages/bundled-features/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +5 -4
  34. package/packages/bundled-features/src/config/handlers/values.query.ts +7 -2
  35. package/packages/bundled-features/src/delivery/attempt-log-retention.ts +40 -0
  36. package/packages/bundled-features/src/delivery/attempt-log.ts +26 -3
  37. package/packages/bundled-features/src/delivery/changes.json +12 -0
  38. package/packages/bundled-features/src/delivery/constants.ts +1 -0
  39. package/packages/bundled-features/src/delivery/events.ts +3 -0
  40. package/packages/bundled-features/src/delivery/feature.ts +50 -2
  41. package/packages/bundled-features/src/delivery/index.ts +6 -0
  42. package/packages/bundled-features/src/delivery/mask-recipient-address.ts +40 -0
  43. package/packages/bundled-features/src/delivery/public-names.ts +6 -1
  44. package/packages/bundled-features/src/delivery/types.ts +12 -2
  45. package/packages/bundled-features/src/derivatives-sharp/changes.json +6 -0
  46. package/packages/bundled-features/src/files-tenant-data/hooks.ts +3 -2
  47. package/packages/bundled-features/src/folders/feature.ts +2 -12
  48. package/packages/bundled-features/src/form-draft/changes.json +7 -1
  49. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +4 -2
  50. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +18 -6
  51. package/packages/bundled-features/src/jobs/handlers/list.query.ts +44 -11
  52. package/packages/bundled-features/src/jobs/i18n.ts +1 -0
  53. package/packages/bundled-features/src/ledger/entity.ts +4 -0
  54. package/packages/bundled-features/src/ledger/schemas.ts +8 -2
  55. package/packages/bundled-features/src/legal-pages/README.md +3 -3
  56. package/packages/bundled-features/src/legal-pages/changes.json +6 -0
  57. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  58. package/packages/bundled-features/src/managed-pages/feature.ts +24 -11
  59. package/packages/bundled-features/src/notes-history/feature.ts +2 -12
  60. package/packages/bundled-features/src/personal-access-tokens/screens.ts +24 -3
  61. package/packages/bundled-features/src/renderer-foundation/types.ts +1 -1
  62. package/packages/bundled-features/src/renderer-simple/changes.json +6 -0
  63. package/packages/bundled-features/src/renderer-simple/feature.ts +4 -6
  64. package/packages/bundled-features/src/renderer-simple/index.ts +1 -0
  65. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +138 -0
  66. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +33 -4
  67. package/packages/bundled-features/src/seo/changes.json +5 -0
  68. package/packages/bundled-features/src/seo/feature.ts +23 -7
  69. package/packages/bundled-features/src/shared/assert-no-where-rule-in-ownership-write.ts +25 -0
  70. package/packages/bundled-features/src/shared/index.ts +2 -1
  71. package/packages/bundled-features/src/shared/rate-limited-text-response.ts +11 -0
  72. package/packages/bundled-features/src/tags/feature.ts +2 -12
  73. package/packages/bundled-features/src/tenant/changes.json +7 -0
  74. package/packages/bundled-features/src/tenant/decrypt-tenant-names.ts +17 -0
  75. package/packages/bundled-features/src/tenant/handlers/me.query.ts +4 -1
  76. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +7 -3
  77. package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +11 -5
  78. package/packages/bundled-features/src/tenant/index.ts +1 -0
  79. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -6
  80. package/packages/bundled-features/src/tenant/screens.ts +1 -1
  81. package/packages/bundled-features/src/tenant-handover/changes.json +6 -0
  82. package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +12 -19
  83. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -0
  84. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +5 -0
  85. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +23 -1
  86. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  87. package/packages/bundled-features/src/tier-engine/index.ts +7 -0
  88. package/packages/bundled-features/src/tier-engine/tier-option-gate.ts +148 -0
  89. package/packages/bundled-features/src/user/handlers/list.query.ts +5 -2
  90. package/packages/bundled-features/src/user-data-rights/boot-checks.ts +7 -0
  91. package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
  92. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +2 -2
  93. package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +36 -6
  94. package/packages/bundled-features/src/workflow-runner/changes.json +5 -0
  95. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +11 -3
  96. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +5 -0
  97. package/samples/apps/marketing-demo/src/app/seed-data.ts +2 -2
  98. package/samples/apps/showcase/src/features/items/schema/item.ts +0 -1
  99. package/samples/apps/styleguide/src/app/shell.tsx +1 -1
  100. package/samples/apps/styleguide/src/features/gallery/web/Gallery.tsx +9 -3
  101. package/samples/apps/styleguide/src/features/widgets/demo-locale.ts +47 -0
  102. package/samples/apps/styleguide/src/features/widgets/feature.ts +48 -15
  103. package/samples/apps/styleguide/src/features/widgets/i18n.ts +28 -0
  104. package/samples/apps/styleguide/src/features/widgets/web/DashboardFilterEcho.tsx +5 -4
  105. package/samples/apps/ui-walkthrough/src/app/assistant-action.tsx +6 -5
  106. package/samples/apps/ui-walkthrough/src/app/shell.tsx +3 -2
  107. package/samples/apps/ui-walkthrough/src/features/tasks/i18n.ts +8 -0
  108. package/samples/recipes/files-post-processing/package.json +1 -1
  109. package/samples/recipes/public-share-token/README.md +4 -5
  110. package/samples/recipes/public-share-token/src/feature.ts +1 -1
  111. package/samples/recipes/security-baseline/README.md +6 -0
  112. package/samples/recipes/seed-migration/README.md +13 -1
  113. package/samples/recipes/tenant-settings/README.md +1 -1
  114. package/packages/bundled-features/src/shared/has-where-rule.ts +0 -11
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
7
7
  import { z } from "zod";
8
+ import { demoMoney, demoMonthYear, demoPercent, demoText } from "./demo-locale";
8
9
  import { WIDGETS_I18N } from "./i18n";
9
10
 
10
11
  // Statische Demo-Zeitreihe (48 Punkte à 30 Minuten) — kein Date-API,
@@ -33,6 +34,13 @@ const INBOX_MESSAGES = Array.from({ length: 18 }, (_, i) => ({
33
34
  unread: i % 4 === 0,
34
35
  }));
35
36
 
37
+ function openAmount(amount: number, locale: string): string {
38
+ return demoText("widgets:dashboard:demo-amount-open", locale).replace(
39
+ "{amount}",
40
+ demoMoney(amount, "EUR", locale),
41
+ );
42
+ }
43
+
36
44
  export const widgetsFeature = defineFeature("widgets", (r) => {
37
45
  r.screen({ id: "widgets", type: "custom", renderer: { react: { __component: "widgets" } } });
38
46
  r.screen({
@@ -129,11 +137,14 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
129
137
  r.queryHandler(
130
138
  "metrics:portfolio-stat",
131
139
  z.object({ region: z.string().optional() }),
132
- async ({ payload: { region } }) => ({
133
- value: region === "us" ? "38.120 $" : region === "eu" ? "54.630 €" : "92.753 €",
134
- sub: "über 4 Konten",
140
+ async ({ payload: { region } }, ctx) => ({
141
+ value:
142
+ region === "us"
143
+ ? demoMoney(38120, "USD", ctx.locale)
144
+ : demoMoney(region === "eu" ? 54630 : 92753, "EUR", ctx.locale),
145
+ sub: demoText("widgets:catalog:portfolio-sub", ctx.locale),
135
146
  tone: "positive",
136
- delta: "12 %",
147
+ delta: demoPercent(0.12, ctx.locale),
137
148
  deltaDirection: "up",
138
149
  deltaTone: "positive",
139
150
  }),
@@ -150,7 +161,7 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
150
161
  r.queryHandler(
151
162
  "metrics:net-worth-assets",
152
163
  z.object({ region: z.string().optional() }),
153
- async () => ({ value: "120.000 €" }),
164
+ async (_query, ctx) => ({ value: demoMoney(120000, "EUR", ctx.locale) }),
154
165
  {
155
166
  access: {
156
167
  openToAll: {
@@ -164,7 +175,7 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
164
175
  r.queryHandler(
165
176
  "metrics:net-worth-debts",
166
177
  z.object({ region: z.string().optional() }),
167
- async () => ({ value: "65.370 €" }),
178
+ async (_query, ctx) => ({ value: demoMoney(65370, "EUR", ctx.locale) }),
168
179
  {
169
180
  access: {
170
181
  openToAll: {
@@ -196,10 +207,18 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
196
207
  r.queryHandler(
197
208
  "metrics:latest-items",
198
209
  z.object({}),
199
- async () => ({
210
+ async (_query, ctx) => ({
200
211
  rows: [
201
- { id: "i1", name: "API-Timeout eu-central", status: "resolved" },
202
- { id: "i2", name: "Zertifikat erneuert", status: "done" },
212
+ {
213
+ id: "i1",
214
+ name: demoText("widgets:dashboard:demo-incident-timeout", ctx.locale),
215
+ status: "resolved",
216
+ },
217
+ {
218
+ id: "i2",
219
+ name: demoText("widgets:dashboard:demo-incident-certificate", ctx.locale),
220
+ status: "done",
221
+ },
203
222
  ],
204
223
  nextCursor: null,
205
224
  }),
@@ -249,10 +268,16 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
249
268
  r.queryHandler(
250
269
  "metrics:upcoming-events",
251
270
  z.object({}),
252
- async () => ({
271
+ async (_query, ctx) => ({
253
272
  rows: [
254
- { primary: "Zinsanpassung Baudarlehen", trailing: "Aug 2026" },
255
- { primary: "Bausparvertrag zuteilungsreif", trailing: "Okt 2026" },
273
+ {
274
+ primary: demoText("widgets:dashboard:demo-event-rate-adjustment", ctx.locale),
275
+ trailing: demoMonthYear(2026, 8, ctx.locale),
276
+ },
277
+ {
278
+ primary: demoText("widgets:dashboard:demo-event-savings-contract", ctx.locale),
279
+ trailing: demoMonthYear(2026, 10, ctx.locale),
280
+ },
256
281
  ],
257
282
  }),
258
283
  {
@@ -268,10 +293,18 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
268
293
  r.queryHandler(
269
294
  "metrics:goal-progress",
270
295
  z.object({}),
271
- async () => ({
296
+ async (_query, ctx) => ({
272
297
  rows: [
273
- { label: "Baudarlehen", value: "42.000 € offen", fraction: 0.71 },
274
- { label: "Autokredit", value: "3.200 € offen", fraction: 0.92 },
298
+ {
299
+ label: demoText("widgets:dashboard:demo-goal-mortgage", ctx.locale),
300
+ value: openAmount(42000, ctx.locale),
301
+ fraction: 0.71,
302
+ },
303
+ {
304
+ label: demoText("widgets:dashboard:demo-goal-car-loan", ctx.locale),
305
+ value: openAmount(3200, ctx.locale),
306
+ fraction: 0.92,
307
+ },
275
308
  ],
276
309
  }),
277
310
  {
@@ -27,6 +27,34 @@ export const WIDGETS_I18N = {
27
27
  "widgets:dashboard:filter-region": { de: "Region", en: "Region" },
28
28
  "widgets:dashboard:filter-region-eu": { de: "Europa", en: "Europe" },
29
29
  "widgets:dashboard:filter-region-us": { de: "USA", en: "USA" },
30
+ "widgets:dashboard:filter-echo-title": { de: "Custom-Panel", en: "Custom panel" },
31
+ "widgets:dashboard:filter-echo-filtered": {
32
+ de: "Gefiltert nach Region: {region}",
33
+ en: "Filtered by region: {region}",
34
+ },
35
+ "widgets:dashboard:filter-echo-unfiltered": {
36
+ de: "Ungefiltert (alle Regionen)",
37
+ en: "Unfiltered (all regions)",
38
+ },
39
+ "widgets:dashboard:demo-incident-timeout": {
40
+ de: "API-Timeout eu-central",
41
+ en: "API timeout eu-central",
42
+ },
43
+ "widgets:dashboard:demo-incident-certificate": {
44
+ de: "Zertifikat erneuert",
45
+ en: "Certificate renewed",
46
+ },
47
+ "widgets:dashboard:demo-event-rate-adjustment": {
48
+ de: "Zinsanpassung Baudarlehen",
49
+ en: "Mortgage rate adjustment",
50
+ },
51
+ "widgets:dashboard:demo-event-savings-contract": {
52
+ de: "Bausparvertrag zuteilungsreif",
53
+ en: "Building savings contract ready for allocation",
54
+ },
55
+ "widgets:dashboard:demo-goal-mortgage": { de: "Baudarlehen", en: "Mortgage" },
56
+ "widgets:dashboard:demo-goal-car-loan": { de: "Autokredit", en: "Car loan" },
57
+ "widgets:dashboard:demo-amount-open": { de: "{amount} offen", en: "{amount} outstanding" },
30
58
  "widgets:catalog:portfolio": { de: "Portfolio", en: "Portfolio" },
31
59
  "widgets:catalog:portfolio-sub": { de: "über 4 Konten", en: "across 4 accounts" },
32
60
  "widgets:catalog:remaining-debt": { de: "Restschuld", en: "Remaining debt" },
@@ -3,18 +3,19 @@
3
3
  // gewählten Screen-Filter-Wert über `filterParams` sieht (siehe
4
4
  // DashboardFilterDefinition in feature.ts).
5
5
 
6
- import type { ExtensionSectionProps } from "@cosmicdrift/kumiko-renderer";
6
+ import { type ExtensionSectionProps, useTranslation } from "@cosmicdrift/kumiko-renderer";
7
7
  import { SectionCard } from "@cosmicdrift/kumiko-renderer-web";
8
8
  import type { ReactNode } from "react";
9
9
 
10
10
  export function DashboardFilterEcho({ filterParams }: ExtensionSectionProps): ReactNode {
11
+ const t = useTranslation();
11
12
  const region = filterParams?.["region"];
12
13
  return (
13
- <SectionCard title="Custom-Panel">
14
+ <SectionCard title={t("widgets:dashboard:filter-echo-title")}>
14
15
  <p className="text-sm text-muted-foreground">
15
16
  {typeof region === "string"
16
- ? `Gefiltert nach Region: ${region}`
17
- : "Ungefiltert (alle Regionen)"}
17
+ ? t("widgets:dashboard:filter-echo-filtered", { region })
18
+ : t("widgets:dashboard:filter-echo-unfiltered")}
18
19
  </p>
19
20
  </SectionCard>
20
21
  );
@@ -1,4 +1,4 @@
1
- import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
1
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
2
2
  import { WandSparkles } from "lucide-react";
3
3
  import { type ReactNode, useEffect, useState } from "react";
4
4
 
@@ -6,6 +6,7 @@ const noop = (): void => {};
6
6
 
7
7
  export function AssistantAction(): ReactNode {
8
8
  const { Dialog } = usePrimitives();
9
+ const t = useTranslation();
9
10
  const [open, setOpen] = useState(false);
10
11
 
11
12
  // Registered here on purpose: an overflow menu that unmounts this component
@@ -30,7 +31,7 @@ export function AssistantAction(): ReactNode {
30
31
  className="inline-flex h-9 items-center gap-2 rounded-md border border-border bg-card px-3 text-sm font-medium text-foreground hover:bg-accent"
31
32
  >
32
33
  <WandSparkles className="h-4 w-4" aria-hidden="true" />
33
- Assistent
34
+ {t("tasks.assistant.label")}
34
35
  <kbd className="rounded border border-border px-1 font-sans text-xs text-muted-foreground">
35
36
  ⌘K
36
37
  </kbd>
@@ -38,9 +39,9 @@ export function AssistantAction(): ReactNode {
38
39
  <Dialog
39
40
  open={open}
40
41
  onOpenChange={setOpen}
41
- title="Assistent"
42
- description="Der Assistent ist im Walkthrough nicht angebunden."
43
- confirmLabel="Schließen"
42
+ title={t("tasks.assistant.label")}
43
+ description={t("tasks.assistant.description")}
44
+ confirmLabel={t("tasks.assistant.close")}
44
45
  onConfirm={noop}
45
46
  testId="assistant-dialog"
46
47
  />
@@ -13,6 +13,7 @@ import {
13
13
  SidebarBrand,
14
14
  ThemeMenuItem,
15
15
  ThemeToggle,
16
+ useTranslation,
16
17
  } from "@cosmicdrift/kumiko-renderer-web";
17
18
  import { MoonStar, Sun } from "lucide-react";
18
19
  import type { ReactNode } from "react";
@@ -20,7 +21,6 @@ import { AssistantAction } from "./assistant-action";
20
21
  import { BETA_TENANT_ID, DEV_TENANT_ID } from "./auth-constants";
21
22
 
22
23
  const APP_NAME = "Kumiko Walkthrough";
23
- const APP_TAGLINE = "Design-Referenz";
24
24
 
25
25
  const tenantName = (tenantId: string): string => {
26
26
  if (tenantId === DEV_TENANT_ID) return "Dev Tenant";
@@ -40,9 +40,10 @@ export function AppShell({
40
40
  readonly children: ReactNode;
41
41
  readonly schema: AppSchema;
42
42
  }): ReactNode {
43
+ const t = useTranslation();
43
44
  return (
44
45
  <DefaultAppShell
45
- brand={<SidebarBrand name={APP_NAME} plan={APP_TAGLINE} />}
46
+ brand={<SidebarBrand name={APP_NAME} plan={t("tasks.shell.tagline")} />}
46
47
  schema={schema}
47
48
  headerActions={
48
49
  <>
@@ -5,6 +5,10 @@ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
5
5
 
6
6
  export const tasksTranslations: TranslationsByLocale = {
7
7
  de: {
8
+ "tasks.shell.tagline": "Design-Referenz",
9
+ "tasks.assistant.label": "Assistent",
10
+ "tasks.assistant.description": "Der Assistent ist im Walkthrough nicht angebunden.",
11
+ "tasks.assistant.close": "Schließen",
8
12
  "tasks.nav.group": "Allgemein",
9
13
  "tasks.nav.list": "Aufgaben",
10
14
  "tasks.nav.new": "Neue Aufgabe",
@@ -21,6 +25,10 @@ export const tasksTranslations: TranslationsByLocale = {
21
25
  "tasks:entity:task:field:notes": "Notizen",
22
26
  },
23
27
  en: {
28
+ "tasks.shell.tagline": "Design reference",
29
+ "tasks.assistant.label": "Assistant",
30
+ "tasks.assistant.description": "The assistant is not connected in the walkthrough.",
31
+ "tasks.assistant.close": "Close",
24
32
  "tasks.nav.group": "General",
25
33
  "tasks.nav.list": "Tasks",
26
34
  "tasks.nav.new": "New task",
@@ -7,6 +7,6 @@
7
7
  "@cosmicdrift/kumiko-framework": "workspace:*"
8
8
  },
9
9
  "devDependencies": {
10
- "sharp": "^0.35.3"
10
+ "sharp": "^0.35.5"
11
11
  }
12
12
  }
@@ -3,9 +3,8 @@
3
3
  Minimal pattern for **tokenized anonymous read surfaces**: authenticated users
4
4
  mint a link; visitors resolve it without login.
5
5
 
6
- Domain-agnostic — no credit/folder logic. Full consumer:
7
- [Cashcolt money-horse](https://github.com/CosmicDriftGameStudio/money-horse)
8
- (`share-by-token` + 5 layout templates).
6
+ Domain-agnostic — no credit/folder logic. A full consumer adds snapshots,
7
+ layout templates and tier gates on top.
9
8
 
10
9
  ## What it shows
11
10
 
@@ -16,8 +15,8 @@ Domain-agnostic — no credit/folder logic. Full consumer:
16
15
  - **`anonymousAccess`** on test stack (see integration test)
17
16
 
18
17
  Client wiring: [`recipes/apex-surface-auth`](../apex-surface-auth/) or a gate
19
- before `createKumikoApp` (money-horse uses `public-share-gate.tsx` like the public
20
- calculator).
18
+ before `createKumikoApp` (a small gate component that renders the public view when
19
+ the URL carries a share token).
21
20
 
22
21
  ## Handlers
23
22
 
@@ -3,7 +3,7 @@
3
3
  // Pattern: authenticated create → plain token show-once → anonymous
4
4
  // read-by-token with SHA-256 lookup. Revoke sets revokedAt via executor.
5
5
  //
6
- // See money-horse for credit/folder snapshots, layouts, tier gates.
6
+ // A full consumer adds domain snapshots, layouts and tier gates on top.
7
7
 
8
8
  import { generateToken } from "@cosmicdrift/kumiko-framework/api";
9
9
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
@@ -54,6 +54,12 @@ export const APP_FEATURES = [
54
54
  For an app that does not mount `dsgvoSelfServiceFeatures()`, use
55
55
  `securityBaselineFeatures()` with its default options, as this recipe does.
56
56
 
57
+ Dedupe keeps the first mounted instance. Late-bound state such as
58
+ `bindAutoRevokeOnPasswordChange` must be bound on the instance returned by
59
+ `composeFeatures`/`createApp`, not on a locally held handle to the dropped one:
60
+ the dropped instance's hooks never run and password changes silently stop
61
+ revoking sessions.
62
+
57
63
  A `createSessionsFeature({ ... })` mounted with different options than the
58
64
  preset's own no-arg instance still produces a clear boot error — dedupe only
59
65
  collapses provably interchangeable instances, never silently picks one:
@@ -5,7 +5,7 @@ File-based migrations for Event-Sourcing operations — like `kumiko schema appl
5
5
  ## What it shows
6
6
 
7
7
  - **`seedsDir`-Option** in `runProdApp({ seedsDir: "./seeds" })` — beim Boot werden pending Files in chronologischer Reihenfolge angewendet und in `kumiko_es_operations` markiert.
8
- - **`SeedMigration`-Interface** — default-Export einer seed-File mit `description` + `run(ctx)`. ctx liefert `systemWriteAs` (System-User bypassed Access-Check) + Read-Helpers (`findUserByEmail`, `findMembershipsOfUser`, `findTenants`).
8
+ - **`SeedMigration`-Interface** — default-Export einer seed-File mit `description` + `run(ctx)`. ctx liefert `systemWriteAs` (System-User bypassed Access-Check) + Read-Helpers (`findUserByEmail`, `findMembershipsOfUser`, `findTenants`, `findTemplateResources`).
9
9
  - **Idempotency** — Marker landet nach Erfolg in der Tracking-Tabelle, zweiter Boot skipped applied Seeds.
10
10
  - **Tx-Atomicity** — Jede Migration läuft in eigener Transaction; Failure rollt zurück + bricht Boot ab (kein Partial-Apply).
11
11
  - **Chronologische File-IDs** — Filename `<date>-<slug>.ts` (z.B. `2026-05-20-fix-admin-roles.ts`) ist die ID.
@@ -35,6 +35,18 @@ seed: 2026-05-20-fix-admin-roles.ts
35
35
 
36
36
  Bei typo / drift fail-t mit klarer message + exit-code 1 — CI nutzt es als prä-build-step. App-Author muss in `smoke.ts` den `features`-Array gegen die eigene App-Feature-Set tauschen (siehe TODO im File).
37
37
 
38
+ ### Archive template rows
39
+
40
+ `findTemplateResources({ slug?, kind?, status?, locale?, tenantId? })` replaces a raw `SELECT … FROM read_template_resources`. It defaults to the system tenant and returns `[]` when template-resolver is not mounted:
41
+
42
+ ```ts
43
+ for (const t of await ctx.findTemplateResources({ kind: "notification", status: "active" })) {
44
+ await ctx.systemWriteAs("template-resolver:write:archive", { id: t.id }, SYSTEM_TENANT_ID, [
45
+ "SystemAdmin",
46
+ ]);
47
+ }
48
+ ```
49
+
38
50
  ## When to reach for it
39
51
 
40
52
  Du hast einen idempotent-Seeder der bei initialer Erstellung „Insert wenn nicht existiert" macht (z.B. `auth.admin.memberships`), und dann ändern sich die Soll-Daten — neue Rolle, anderer Tier, korrigierte Bezeichnung. Idempotent-Seeder skippen die existing Rows → DB driftet vom Code-Stand ab.
@@ -20,7 +20,7 @@ import { defineCreateWithTenantDefaults } from "@cosmicdrift/kumiko-bundled-feat
20
20
  export const invoiceEntity = createEntity({
21
21
  table: "read_invoices",
22
22
  fields: {
23
- title: createTextField({ required: true }),
23
+ title: createTextField({ personal: false, reason: "is_business_data", required: true }),
24
24
  amount: createMoneyField({ required: true }),
25
25
  language: createSelectField({ options: ["en", "de", "fr"] as const }),
26
26
  },
@@ -1,11 +0,0 @@
1
- import type { OwnershipMap } from "@cosmicdrift/kumiko-framework/engine";
2
-
3
- // `where`-rules are read-path only (buildOwnershipClause). The write path
4
- // decides in memory against the concrete row, so a where-rule there can only
5
- // ever deny — boot validation rejects the shape (fw#2626). Feature factories
6
- // that accept an `ownership` option reject it earlier, at build time, so the
7
- // author gets the option name in the message instead of an entity scope.
8
- export function hasWhereRule(map: OwnershipMap | undefined): boolean {
9
- if (!map) return false;
10
- return Object.values(map).some((rule) => rule !== "all" && rule.kind === "where");
11
- }