@cosmicdrift/kumiko-samples 0.391.0 → 0.392.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 (138) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +1 -1
  3. package/packages/bundled-features/src/admin-shell/changes.json +6 -0
  4. package/packages/bundled-features/src/admin-shell/feature.ts +60 -31
  5. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +2 -2
  6. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +2 -2
  7. package/packages/bundled-features/src/cap-overview/changes.json +12 -0
  8. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -0
  9. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +3 -2
  10. package/packages/bundled-features/src/cap-overview/i18n.ts +4 -0
  11. package/packages/bundled-features/src/cap-overview/types.ts +4 -1
  12. package/packages/bundled-features/src/cap-overview/web/cap-amount-format.ts +37 -0
  13. package/packages/bundled-features/src/cap-overview/web/cap-cards-panel.tsx +138 -54
  14. package/packages/bundled-features/src/cap-overview/web/cap-usage-bar.tsx +18 -4
  15. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -3
  16. package/packages/bundled-features/src/cap-overview/web/usage-math.ts +5 -1
  17. package/packages/bundled-features/src/channel-email/app-transport.ts +37 -0
  18. package/packages/bundled-features/src/channel-email/changes.json +6 -0
  19. package/packages/bundled-features/src/channel-email/feature.ts +6 -0
  20. package/packages/bundled-features/src/channel-email/index.ts +5 -0
  21. package/packages/bundled-features/src/channel-email/smtp-transport.ts +2 -22
  22. package/packages/bundled-features/src/foundation-shared/changes.json +6 -0
  23. package/packages/bundled-features/src/foundation-shared/index.ts +1 -0
  24. package/packages/bundled-features/src/foundation-shared/mail-display-name.ts +20 -0
  25. package/packages/bundled-features/src/inbound-mail-foundation/account-record.ts +48 -0
  26. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +18 -0
  27. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
  28. package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +17 -0
  29. package/packages/bundled-features/src/inbound-mail-foundation/index.ts +18 -0
  30. package/packages/bundled-features/src/inbound-mail-foundation/outbound-mail.ts +66 -0
  31. package/packages/bundled-features/src/inbound-mail-foundation/probe.ts +8 -45
  32. package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +14 -0
  33. package/packages/bundled-features/src/inbound-mail-foundation/send-from-account.ts +118 -0
  34. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +8 -0
  35. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +13 -8
  36. package/packages/bundled-features/src/inbound-provider-imap/changes.json +12 -0
  37. package/packages/bundled-features/src/inbound-provider-imap/connect-mailbox.ts +156 -0
  38. package/packages/bundled-features/src/inbound-provider-imap/constants.ts +42 -0
  39. package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +49 -1
  40. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +12 -35
  41. package/packages/bundled-features/src/inbound-provider-imap/i18n.ts +48 -0
  42. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +1 -1
  43. package/packages/bundled-features/src/inbound-provider-imap/screens.ts +44 -0
  44. package/packages/bundled-features/src/inbound-provider-imap/send-mail.ts +73 -0
  45. package/packages/bundled-features/src/inbound-provider-imap/sent-folder.ts +49 -0
  46. package/packages/bundled-features/src/inbound-provider-imap/smtp-client.ts +131 -0
  47. package/packages/bundled-features/src/legal-pages/README.md +5 -10
  48. package/packages/bundled-features/src/mail-foundation/changes.json +6 -0
  49. package/packages/bundled-features/src/mail-foundation/i18n.ts +9 -1
  50. package/packages/bundled-features/src/mail-foundation/probe.ts +43 -22
  51. package/packages/bundled-features/src/readiness/built-in-probes.ts +15 -2
  52. package/packages/bundled-features/src/readiness/changes.json +30 -0
  53. package/packages/bundled-features/src/readiness/constants.ts +2 -0
  54. package/packages/bundled-features/src/readiness/i18n.ts +7 -1
  55. package/packages/bundled-features/src/readiness/index.ts +6 -1
  56. package/packages/bundled-features/src/readiness/screens.ts +25 -2
  57. package/packages/bundled-features/src/readiness/visible-results.ts +2 -1
  58. package/packages/bundled-features/src/renderer-foundation/README.md +19 -21
  59. package/packages/bundled-features/src/template-resolver/README.md +144 -165
  60. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  61. package/packages/bundled-features/src/tier-engine/feature.ts +17 -6
  62. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +3 -3
  63. package/packages/bundled-features/src/user-data-rights/schema/download-token.ts +5 -3
  64. package/packages/bundled-features/src/user-data-rights/token-helpers.ts +5 -4
  65. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  66. package/samples/apps/cap-billing-demo/bin/server.ts +7 -7
  67. package/samples/apps/cap-billing-demo/src/feature.ts +38 -38
  68. package/samples/apps/cap-billing-demo/src/run-config.ts +20 -20
  69. package/samples/apps/cap-billing-demo/src/tier-map.ts +14 -14
  70. package/samples/apps/config-demo/src/app/server.ts +4 -4
  71. package/samples/apps/config-demo/src/features/demo/feature.ts +4 -4
  72. package/samples/apps/marketing-demo/playwright.config.ts +3 -3
  73. package/samples/apps/marketing-demo/src/app/client.tsx +2 -2
  74. package/samples/apps/marketing-demo/src/app/seed-data.ts +9 -9
  75. package/samples/apps/marketing-demo/src/app/seed.ts +14 -14
  76. package/samples/apps/marketing-demo/src/app/server.ts +7 -7
  77. package/samples/apps/marketing-demo/src/app/shell.tsx +3 -3
  78. package/samples/apps/marketing-demo/src/features/assets/feature.ts +3 -3
  79. package/samples/apps/marketing-demo/src/features/assets/i18n.ts +7 -7
  80. package/samples/apps/marketing-demo/src/features/assets/web/index.ts +2 -2
  81. package/samples/apps/showcase/src/app/client.tsx +3 -3
  82. package/samples/apps/showcase/src/app/seed-items.ts +14 -14
  83. package/samples/apps/showcase/src/app/server.ts +7 -7
  84. package/samples/apps/showcase/src/features/demos/feature.ts +13 -13
  85. package/samples/apps/showcase/src/features/demos/i18n.ts +2 -2
  86. package/samples/apps/showcase/src/features/demos/web/components/page.tsx +5 -5
  87. package/samples/apps/showcase/src/features/demos/web/index.ts +4 -4
  88. package/samples/apps/showcase/src/features/demos/web/pages/dialog.tsx +1 -1
  89. package/samples/apps/showcase/src/features/demos/web/pages/inputs.tsx +8 -8
  90. package/samples/apps/showcase/src/features/demos/web/pages/layout.tsx +5 -5
  91. package/samples/apps/showcase/src/features/demos/web/pages/toast.tsx +3 -3
  92. package/samples/apps/showcase/src/features/items/feature.ts +8 -8
  93. package/samples/apps/showcase/src/features/items/i18n.ts +3 -3
  94. package/samples/apps/showcase/src/features/items/index.ts +3 -3
  95. package/samples/apps/showcase/src/features/items/schema/index.ts +4 -4
  96. package/samples/apps/showcase/src/features/items/schema/item.ts +27 -27
  97. package/samples/apps/showcase/src/features/items/web/index.ts +6 -6
  98. package/samples/apps/styleguide/src/app/seed.ts +3 -3
  99. package/samples/apps/styleguide/src/app/server.ts +2 -2
  100. package/samples/apps/styleguide/src/app/shell.tsx +4 -4
  101. package/samples/apps/styleguide/src/features/content/web.tsx +6 -6
  102. package/samples/apps/styleguide/src/features/examples/feature.ts +8 -8
  103. package/samples/apps/styleguide/src/features/gallery/feature.ts +9 -9
  104. package/samples/apps/styleguide/src/features/gallery/web/Gallery.tsx +12 -12
  105. package/samples/apps/styleguide/src/features/widgets/feature.ts +2 -2
  106. package/samples/apps/styleguide/src/features/widgets/web/DashboardFilterEcho.tsx +3 -3
  107. package/samples/apps/styleguide/src/features/widgets/web/DashboardKpiIcon.tsx +4 -4
  108. package/samples/apps/ui-walkthrough/playwright.config.ts +6 -6
  109. package/samples/apps/ui-walkthrough/scripts/emit-e2e-data.ts +5 -5
  110. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +13 -13
  111. package/samples/apps/ui-walkthrough/src/app/client.tsx +2 -2
  112. package/samples/apps/ui-walkthrough/src/app/server.ts +14 -14
  113. package/samples/apps/ui-walkthrough/src/app/shell.tsx +3 -3
  114. package/samples/apps/use-all-bundled/bin/main.ts +5 -5
  115. package/samples/apps/use-all-bundled/feature-manifest.json +10 -4
  116. package/samples/apps/use-all-bundled/kumiko/schema.ts +7 -7
  117. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +20 -20
  118. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +4 -4
  119. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -4
  120. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +12 -12
  121. package/samples/apps/use-all-bundled/src/app/seed.ts +6 -6
  122. package/samples/apps/user-data-rights-demo/src/feature.ts +23 -23
  123. package/samples/apps/workspaces/playwright.config.ts +5 -5
  124. package/samples/apps/workspaces/src/app/auth-constants.ts +3 -3
  125. package/samples/apps/workspaces/src/app/server.ts +16 -16
  126. package/samples/apps/workspaces/src/app/shell.tsx +2 -2
  127. package/samples/apps/workspaces/src/features/demo/schema/order.ts +4 -4
  128. package/samples/apps/workspaces/src/features/demo-driver/feature.ts +5 -5
  129. package/samples/recipes/apex-surface-auth/src/feature.ts +31 -31
  130. package/samples/recipes/designer-demo/src/feature.ts +6 -6
  131. package/samples/recipes/encrypted-tenant-config/src/feature.ts +20 -20
  132. package/samples/recipes/lane-routing/src/feature.ts +5 -5
  133. package/samples/recipes/legal-pages/src/feature.ts +29 -29
  134. package/samples/recipes/record-detail-layout/src/feature.ts +21 -2
  135. package/samples/recipes/seed-migration/scripts/smoke.ts +20 -20
  136. package/samples/recipes/seed-migration/seeds/2026-01-01-initial-tenants.ts +11 -11
  137. package/samples/recipes/seed-migration/seeds/2026-02-01-fix-admin-roles.ts +13 -13
  138. package/samples/recipes/user-data-rights/src/feature.ts +13 -13
@@ -1,18 +1,18 @@
1
- // apex-surface-auth Recipe — der evidente Weg für öffentlichen Apex-Content.
1
+ // apex-surface-auth recipe — the evident way for public apex content.
2
2
  //
3
- // Eine Kumiko-App hat eine öffentliche Apex-Präsenz (Landing/Login) UND eine
4
- // schema-getriebene Admin-UI. Die Admin-UI mountet via createKumikoApp (volles
5
- // Schema). Die Apex mountet via createPublicSurface — schema-LOS, anonym
6
- // erreichbar, kein Admin-Nav/Topologie-Leak. Beide teilen Locale + Primitives.
3
+ // A Kumiko app has a public apex presence (landing/login) AND a
4
+ // schema-driven admin UI. The admin UI mounts via createKumikoApp (full
5
+ // schema). The apex mounts via createPublicSurface — schema-LESS, anonymously
6
+ // reachable, no admin nav/topology leak. Both share locale + primitives.
7
7
  //
8
- // Dieses Recipe zeigt:
9
- // 1. Server: die Feature-Komposition für die 4 Account-Flows + den anonymen,
10
- // email-verifizierten Deletion-Flow (Lockout-sicher).
11
- // 2. Client (Kommentar unten + README): wie createPublicSurface + AuthShell
12
- // die Screens in der Apex-Chrome mounten.
8
+ // This recipe shows:
9
+ // 1. Server: the feature composition for the 4 account flows + the anonymous,
10
+ // email-verified deletion flow (lockout-safe).
11
+ // 2. Client (comment below + README): how createPublicSurface + AuthShell
12
+ // mount the screens in the apex chrome.
13
13
  //
14
- // CLIENT-WIRING (apex.tsx der App — renderer/web, hier nur als Referenz, da
15
- // Recipes keine Browser-Deps ziehen):
14
+ // CLIENT-WIRING (the app's apex.tsx — renderer/web, only a reference here, since
15
+ // recipes pull in no browser deps):
16
16
  //
17
17
  // import {
18
18
  // ForgotPasswordScreen, SignupScreen, createLoginRoute,
@@ -24,8 +24,8 @@
24
24
  // } from "@cosmicdrift/kumiko-bundled-features/user-data-rights/web";
25
25
  // import { createPublicSurface } from "@cosmicdrift/kumiko-renderer/web";
26
26
  //
27
- // // AuthShell: die Auth-Card rendert in der Marketing-Chrome statt
28
- // // Fullscreen. Default (ohne Provider) bleibt der Fullscreen-Wrapper.
27
+ // // AuthShell: the auth card renders inside the marketing chrome instead of
28
+ // // fullscreen. Default (without a provider) stays the fullscreen wrapper.
29
29
  // const shell = ({ children }) => (
30
30
  // <MarketingChrome>
31
31
  // <AuthShellProvider shell={(card) => <div className="py-12 flex justify-center">{card}</div>}>
@@ -34,12 +34,12 @@
34
34
  // </MarketingChrome>
35
35
  // );
36
36
  //
37
- // // createLoginRoute — NICHT LoginScreen direkt rendern: die Route
38
- // // braucht die Challenge-Swap-Logik für einen zweiten Faktor. Ohne die
39
- // // (z.B. raw <LoginScreen />) bleibt ein MFA-enrolter User beim Login
40
- // // hängen, sobald die App irgendwann auth-mfa mountet. Mountet die App
41
- // // auth-mfa, `mfaVerifyScreen: MfaVerifyScreen` (aus
42
- // // "@cosmicdrift/kumiko-bundled-features/auth-mfa/web") ergänzen.
37
+ // // createLoginRoute — do NOT render LoginScreen directly: the route
38
+ // // needs the challenge-swap logic for a second factor. Without it
39
+ // // (e.g. a raw <LoginScreen />) an MFA-enrolled user gets stuck at login
40
+ // // as soon as the app mounts auth-mfa at some point. If the app mounts
41
+ // // auth-mfa, add `mfaVerifyScreen: MfaVerifyScreen` (from
42
+ // // "@cosmicdrift/kumiko-bundled-features/auth-mfa/web").
43
43
  // const LoginRoute = createLoginRoute({ loginScreenProps: { signupHref: "/signup" } });
44
44
  //
45
45
  // createPublicSurface({
@@ -56,8 +56,8 @@
56
56
  // });
57
57
  //
58
58
  //
59
- // SERVER-WIRING: die App aktiviert `anonymousAccess` (defaultTenantId = der
60
- // Apex-Host-Tenant), damit /api/write die anonymen Deletion-Handler erreicht:
59
+ // SERVER-WIRING: the app enables `anonymousAccess` (defaultTenantId = the
60
+ // apex host tenant) so /api/write reaches the anonymous deletion handlers:
61
61
  //
62
62
  // runProdApp({ features: composeApexAccountApp({...}), envSchema,
63
63
  // anonymousAccess: { defaultTenantId: APEX_TENANT_ID } })
@@ -78,20 +78,20 @@ import {
78
78
  import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
79
79
 
80
80
  export type ApexAccountAppOptions = {
81
- /** HMAC-Secret für das Deletion-Verify-Token. */
81
+ /** HMAC secret for the deletion verify token. */
82
82
  readonly deletionTokenSecret: string;
83
- /** Apex-Route des Confirm-Screens; der Handler hängt das Token als
84
- * URL-Fragment an (`#token=`). */
83
+ /** Apex route of the confirm screen; the handler appends the token as a
84
+ * URL fragment (`#token=`). */
85
85
  readonly deletionVerifyUrl: string;
86
- /** Versand des Verify-Magic-Links. MUSS non-blocking sein (enqueue) — ein
87
- * synchroner Send würde ein Timing-Oracle für Account-Enumeration öffnen. */
86
+ /** Sending the verify magic link. MUST be non-blocking (enqueue) — a
87
+ * synchronous send would open a timing oracle for account enumeration. */
88
88
  readonly sendDeletionVerificationEmail: SendDeletionVerificationEmailFn;
89
89
  };
90
90
 
91
- // Volle Feature-Komposition für die öffentlichen Account-Flows. Die
92
- // user-data-rights-Options aktivieren den anonymen Deletion-Flow; die übrigen
93
- // Features liefern Login/Register/PW (auth-email-password) + die
94
- // Require-Kette (user-data-rights → data-retention + compliance-profiles +
91
+ // Full feature composition for the public account flows. The
92
+ // user-data-rights options enable the anonymous deletion flow; the remaining
93
+ // features provide login/register/PW (auth-email-password) + the
94
+ // require chain (user-data-rights → data-retention + compliance-profiles +
95
95
  // sessions).
96
96
  export function composeApexAccountApp(opts: ApexAccountAppOptions): FeatureDefinition[] {
97
97
  return [
@@ -1,10 +1,10 @@
1
1
  // kumiko-feature-version: 1
2
- // Demo-Feature für den Designer-Skeleton (C5).
3
- // Enthält bewusst Patterns aus den 3 hand-rollten View-Kategorien:
4
- // - entity → Tabelle der Fields
5
- // - writeHandler → Header + Code-Blocks (schema + handler)
6
- // - nav → ID/Label/Screen-Card
7
- // Plus optionalRequires + metric als JSON-Dump-Fallback.
2
+ // Demo feature for the designer skeleton (C5).
3
+ // Deliberately contains patterns from the 3 hand-rolled view categories:
4
+ // - entity → table of fields
5
+ // - writeHandler → header + code blocks (schema + handler)
6
+ // - nav → ID/label/screen card
7
+ // Plus optionalRequires + metric as a JSON dump fallback.
8
8
 
9
9
  import { createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
10
10
  import { z } from "zod";
@@ -1,14 +1,14 @@
1
- // Encrypted per-tenant config — Stripe-API-key pattern.
1
+ // Encrypted per-tenant config — Stripe API key pattern.
2
2
  //
3
- // Minimal-Showcase: ein dummy "billing"-feature mit einem per-tenant
4
- // Stripe-API-Key. Der `mask`-Eintrag lässt den configEdit-Screen +
5
- // Settings-Hub-Nav automatisch entstehen (kein handgeschriebenes
6
- // r.screen/r.nav mehr). charge-handler liest den Key über ctx.config
7
- // (entschlüsselt automatisch).
3
+ // Minimal showcase: a dummy "billing" feature with a per-tenant
4
+ // Stripe API key. The `mask` entry makes the configEdit screen +
5
+ // settings hub nav appear automatically (no more hand-written
6
+ // r.screen/r.nav). The charge handler reads the key via ctx.config
7
+ // (decrypted automatically).
8
8
  //
9
- // Production: nicht aufrufen — der echte Stripe-Call ist hier ein Mock.
10
- // Pattern in produktiven Apps: Strip-API-Key in tenantBillingConfig.key,
11
- // charge-handler lädt key + ruft tatsächlich Stripe.
9
+ // Production: do not call — the real Stripe call is a mock here.
10
+ // Pattern in productive apps: Stripe API key in tenantBillingConfig.key,
11
+ // the charge handler loads the key + actually calls Stripe.
12
12
 
13
13
  import {
14
14
  access,
@@ -40,8 +40,8 @@ const stripeApiKeyHandle: ConfigKeyHandle<"text"> = {
40
40
  type: "text",
41
41
  };
42
42
 
43
- // Charge-Handler — nutzt den entschlüsselten API-Key. Caller sieht NUR
44
- // die charge-id zurück, der Key bleibt server-side.
43
+ // Charge handler — uses the decrypted API key. The caller sees ONLY
44
+ // the charge id back, the key stays server-side.
45
45
  const chargeHandler = defineWriteHandler({
46
46
  name: "charge",
47
47
  schema: z.object({
@@ -66,10 +66,10 @@ const chargeHandler = defineWriteHandler({
66
66
  );
67
67
  }
68
68
 
69
- // Mock: real impl würde fetch("https://api.stripe.com/v1/charges",
70
- // { headers: { Authorization: `Bearer ${apiKey}` }, ... }) ausführen.
71
- // Wichtig: apiKey verlässt den server NICHT — kein log, kein
72
- // response-field, kein error-detail.
69
+ // Mock: a real impl would execute fetch("https://api.stripe.com/v1/charges",
70
+ // { headers: { Authorization: `Bearer ${apiKey}` }, ... }).
71
+ // Important: apiKey does NOT leave the server — no log, no
72
+ // response field, no error detail.
73
73
  const chargeId = `ch_${Date.now()}_${event.payload.customerRef}`;
74
74
 
75
75
  return {
@@ -83,10 +83,10 @@ export const billingFeature = defineFeature(FEATURE, (r) => {
83
83
  r.requires({ features: ["config"] });
84
84
  r.config("stripe-api-key", stripeApiKeyDef);
85
85
  r.writeHandler(chargeHandler);
86
- // Kein r.screen/r.nav: der `mask`-Eintrag auf dem Key lässt
87
- // buildConfigFeatureSchema den configEdit-Screen (••••••-maskiert,
88
- // config:write:set verschlüsselt vor dem write) + den Settings-Hub-Nav
89
- // automatisch ableiten.
86
+ // No r.screen/r.nav: the `mask` entry on the key lets
87
+ // buildConfigFeatureSchema derive the configEdit screen (••••••-masked,
88
+ // config:write:set encrypts before the write) + the settings hub nav
89
+ // automatically.
90
90
 
91
91
  r.translations({
92
92
  keys: {
@@ -96,5 +96,5 @@ export const billingFeature = defineFeature(FEATURE, (r) => {
96
96
  });
97
97
  });
98
98
 
99
- // Re-exports damit tests die handles ohne re-typing nutzen können.
99
+ // Re-exports so tests can use the handles without re-typing.
100
100
  export { stripeApiKeyHandle };
@@ -24,11 +24,11 @@ import { z } from "zod";
24
24
  export const renderedReceipts: Array<{ customerName: string; amount: number }> = [];
25
25
  export const sentConfirmations: Array<{ customerName: string; amount: number }> = [];
26
26
 
27
- // Entity-Dekl sichert den qualified-name-Prefix "orders:write:order:create"
28
- // für den Write-Handler unten und damit den Job-Trigger auf diesem Event.
29
- // Persistenz passiert im Custom-Handler nicht (id ist hardcoded für den
30
- // Sample-Test) — in einer echten App würde ein entityWriteHandler in die DB
31
- // schreiben.
27
+ // The entity declaration secures the qualified-name prefix "orders:write:order:create"
28
+ // for the write handler below and thereby the job trigger on this event.
29
+ // Persistence does not happen in the custom handler (id is hardcoded for the
30
+ // sample test) — in a real app an entityWriteHandler would write to the
31
+ // DB.
32
32
  const orderEntity = createEntity({
33
33
  table: "read_orders",
34
34
  fields: {
@@ -1,37 +1,37 @@
1
- // Legal-Pages Sample
1
+ // Legal-pages sample
2
2
  //
3
- // DACH-Apps (DE/AT/CH) sind verpflichtet ein Impressum (TMG/DDG §5) und
4
- // eine Datenschutzerklärung (DSGVO Art. 13) öffentlich zugänglich zu
5
- // haben. Das ist 1) für jede App identisch und 2) nervig manuell pro
6
- // App neu zu basteln.
3
+ // DACH apps (DE/AT/CH) are required to make an imprint (TMG/DDG §5) and
4
+ // a privacy policy (GDPR Art. 13) publicly accessible.
5
+ // That is 1) identical for every app and 2) annoying to build
6
+ // by hand per app.
7
7
  //
8
- // Lösung: zwei opt-in bundled-features kombinieren:
8
+ // Solution: combine two opt-in bundled features:
9
9
  //
10
- // - `template-resolver` — der Content-Store (Entity `template-resource`,
11
- // kind `text-block` mit slug+locale+title+content, scoped per Tenant).
12
- // Auch nutzbar für FAQ, About, ToS, Marketing-Snippets — nicht
13
- // legal-spezifisch.
10
+ // - `template-resolver` — the content store (entity `template-resource`,
11
+ // kind `text-block` with slug+locale+title+content, scoped per tenant).
12
+ // Also usable for FAQ, About, ToS, marketing snippets — not
13
+ // legal-specific.
14
14
  //
15
- // - `legal-pages` — opt-in-Wrapper darauf, der vier feste Public-
16
- // Routes (`/legal/impressum`, `/legal/datenschutz`, `/legal/imprint`,
17
- // `/legal/privacy`) registriert und Markdown→HTML rendered. Plus
18
- // Boot-Check der in Production hart fehlt wenn die DE-Pflicht-Blocks
19
- // fehlen.
15
+ // - `legal-pages` — opt-in wrapper on top of it that registers four fixed public
16
+ // routes (`/legal/impressum`, `/legal/datenschutz`, `/legal/imprint`,
17
+ // `/legal/privacy`) and renders Markdown→HTML. Plus a
18
+ // boot check that fails hard in production when the DE required blocks
19
+ // are missing.
20
20
  //
21
- // Tenant-Modell: 1 App = X Tenants = 1 Impressum. Alle Subdomains
22
- // teilen sich die SYSTEM_TENANT_ID-Version. Wer pro-Tenant-Impressums
23
- // braucht, muss sein eigenes Routing davorsetzen oder die by-slug-
24
- // query mit tenant-specific tenantId nutzen.
21
+ // Tenant model: 1 app = X tenants = 1 imprint. All subdomains
22
+ // share the SYSTEM_TENANT_ID version. If you need per-tenant imprints,
23
+ // you have to put your own routing in front or use the by-slug
24
+ // query with a tenant-specific tenantId.
25
25
  //
26
- // Voraussetzungen für Production:
27
- // - `anonymousAccess` muss in runProdApp/runDevApp konfiguriert sein
28
- // (defaultTenantId = SYSTEM_TENANT_ID), sonst antworten die
29
- // legal-pages-Routes mit 503
30
- // - `extraContext.templateResolver = createTemplateResolverApi(db, registry)` muss
31
- // gewired sein, sonst wirft der Boot-Check mit Wiring-Hinweis
32
- // - Beim ersten Boot müssen die TextBlocks geseedet sein —
33
- // template-resolver/seeding `seedTextBlock` oder via API
34
- // `template-resolver:write:set` mit TenantAdmin-Token
26
+ // Prerequisites for production:
27
+ // - `anonymousAccess` must be configured in runProdApp/runDevApp
28
+ // (defaultTenantId = SYSTEM_TENANT_ID), otherwise the
29
+ // legal-pages routes answer with 503
30
+ // - `extraContext.templateResolver = createTemplateResolverApi(db, registry)` must be
31
+ // wired, otherwise the boot check throws with a wiring hint
32
+ // - On first boot the TextBlocks must be seeded —
33
+ // template-resolver/seeding `seedTextBlock` or via API
34
+ // `template-resolver:write:set` with a TenantAdmin token
35
35
 
36
36
  import {
37
37
  createLegalPagesFeature,
@@ -57,5 +57,5 @@ export const legalPagesFeature = createLegalPagesFeature({
57
57
  }),
58
58
  });
59
59
 
60
- // Re-exports für Tests + andere Demos
60
+ // Re-exports for tests + other demos
61
61
  export { LEGAL_REQUIRED_BLOCKS, LEGAL_ROUTES, LEGAL_TERMS_ROUTES };
@@ -11,8 +11,10 @@
11
11
  // one `extension` section (a plugin-mounted component, fw#3234 padding-
12
12
  // parity check — every tab kind must sit in the same Card padding, no
13
13
  // section carries extra chrome of its own).
14
- // 4. A screen-level `actions` entry (fw#3234) renders next to the header,
15
- // not inside any tab's Card.
14
+ // 4. The payments tab's emptyState picks its text from the order record through `variants`.
15
+ // 5. A screen-level `actions` entry (fw#3234) renders next to the header,
16
+ // not inside any tab's Card. `inlineActions` keeps download-invoice a visible
17
+ // button next to it where the page header would otherwise fold it into the menu.
16
18
  //
17
19
  // projectionDetail has no entity — it binds to an explicit query instead, so
18
20
  // every field name below (header, metrics, fields) is a column of that
@@ -108,6 +110,15 @@ export function createOrderDeskFeature(): FeatureDefinition {
108
110
  "method",
109
111
  "status",
110
112
  ],
113
+ emptyState: {
114
+ title: "order-desk.payments.empty",
115
+ variants: [
116
+ {
117
+ when: { field: "status", eq: "pending" },
118
+ title: "order-desk.payments.empty-pending",
119
+ },
120
+ ],
121
+ },
111
122
  },
112
123
  {
113
124
  id: "details",
@@ -132,7 +143,15 @@ export function createOrderDeskFeature(): FeatureDefinition {
132
143
  screen: "order-detail",
133
144
  icon: "refresh",
134
145
  },
146
+ {
147
+ kind: "navigate",
148
+ id: "download-invoice",
149
+ label: "order-desk.action.downloadInvoice",
150
+ screen: "order-detail",
151
+ icon: "download",
152
+ },
135
153
  ],
154
+ inlineActions: ["download-invoice"],
136
155
  access: { roles: ["Support", "Admin"] },
137
156
  });
138
157
  });
@@ -1,40 +1,40 @@
1
1
  // biome-ignore-all lint/suspicious/noConsole: CLI tool output
2
2
  //
3
- // Lokaler Smoke-Test für seed-migrations.
3
+ // Local smoke test for seed migrations.
4
4
  //
5
- // Was er prüft (offline, ohne Pod-Deploy):
6
- // 1. Alle seed-files in ./seeds/ können dynamic-import geladen werden
7
- // → catched syntax-errors + missing default-exports + falsche paths
8
- // 2. Jeder handler-QN den die seeds referenzieren ist in der echten
9
- // App-Registry registriert → catched camelCase-typos + drift
10
- // (Bug-Klasse 3 aus Phase 1 retro)
11
- // 3. Jeder system-user (mit oder ohne tenantIdOverride) hat Access auf
12
- // die referenzierten Handler → catched access-rule-Drift
13
- // (Bug-Klasse 4 aus Phase 1 retro)
5
+ // What it checks (offline, without a pod deploy):
6
+ // 1. All seed files in ./seeds/ can be loaded via dynamic import
7
+ // → catches syntax errors + missing default exports + wrong paths
8
+ // 2. Every handler QN the seeds reference is registered in the real
9
+ // app registry → catches camelCase typos + drift
10
+ // (bug class 3 from the phase 1 retro)
11
+ // 3. Every system user (with or without tenantIdOverride) has access to
12
+ // the referenced handlers → catches access rule drift
13
+ // (bug class 4 from the phase 1 retro)
14
14
  //
15
- // Schreibt NICHTS in die DB. Reine Read-only Validation.
15
+ // Writes NOTHING to the DB. Pure read-only validation.
16
16
  //
17
17
  // Usage:
18
- // bun scripts/smoke.ts (gegen ./seeds/)
18
+ // bun scripts/smoke.ts (against ./seeds/)
19
19
  // bun scripts/smoke.ts --seeds-dir foo (custom path)
20
20
  //
21
- // In CI: vor `build`-Step laufen lassen — fängt 80% der Phase-1-Bugs
22
- // lokal in <5 Sekunden statt nach 8-Minuten-Docker-Build im Pod-Boot.
21
+ // In CI: run before the `build` step — catches 80% of the phase 1 bugs
22
+ // locally in <5 seconds instead of after an 8-minute Docker build in the pod boot.
23
23
 
24
24
  import { existsSync, readdirSync, readFileSync } from "node:fs";
25
25
  import { resolve } from "node:path";
26
- // App-spezifisch: nimm die EFFEKTIVEN Features die deine App zur Runtime
27
- // nutzt. Im typischen Setup mit `runProdApp({ features, auth })`:
26
+ // App-specific: take the EFFECTIVE features your app uses
27
+ // at runtime. In the typical setup with `runProdApp({ features, auth })`:
28
28
  //
29
29
  // import { composeFeatures } from "@cosmicdrift/kumiko-dev-server";
30
30
  // import { APP_FEATURES } from "../src/run-config";
31
31
  // const composed = composeFeatures(APP_FEATURES, { includeBundled: true, ... });
32
32
  // const features = composed.features;
33
33
  //
34
- // Bei reiner App-Feature-Setup (kein auth-mode) reicht ein direkter
35
- // Import der App-Features. Hier im Recipe der minimale set für die
36
- // referenzierten Recipe-Seeds (tenant + config) — App-Author tauscht
37
- // gegen sein eigenes Feature-Set.
34
+ // For a pure app-feature setup (no auth mode) a direct
35
+ // import of the app features is enough. Here in the recipe the minimal set for the
36
+ // referenced recipe seeds (tenant + config) — the app author swaps it
37
+ // for their own feature set.
38
38
  import { createConfigFeature } from "@cosmicdrift/kumiko-bundled-features/config";
39
39
  import { createTenantFeature } from "@cosmicdrift/kumiko-bundled-features/tenant";
40
40
  import {
@@ -1,22 +1,22 @@
1
- // Initial-Seed: Demo-Tenants anlegen.
1
+ // Initial seed: create demo tenants.
2
2
  //
3
- // Note: in real apps wäre "initial tenants" wahrscheinlich nicht als
4
- // seed-migration sondern als `r.config({seeds})` oder `auth.admin`-
5
- // Config umgesetzt — die laufen jedes-Boot idempotent. Hier ist es als
6
- // seed-migration gezeigt um das Pattern zu demonstrieren.
3
+ // Note: in real apps "initial tenants" would probably not be implemented as a
4
+ // seed migration but as `r.config({seeds})` or `auth.admin`
5
+ // config — those run idempotently on every boot. Here it is shown as a
6
+ // seed migration to demonstrate the pattern.
7
7
  //
8
- // Pattern: jede seed-migration arbeitet write-only via `systemWriteAs`
9
- // damit alles als Event-Stream persistiert wird. Read-only Lookups
10
- // (find*) am Context oder direkt via ctx.db.
8
+ // Pattern: every seed migration works write-only via `systemWriteAs`
9
+ // so everything is persisted as an event stream. Read-only lookups
10
+ // (find*) on the context or directly via ctx.db.
11
11
 
12
12
  import type { SeedMigration } from "@cosmicdrift/kumiko-framework/es-ops";
13
13
 
14
14
  export default {
15
15
  description: "initial demo tenants (alice-corp, beta-inc)",
16
16
  run: async (ctx) => {
17
- // Idempotent-Check: wenn Tenants schon existieren (z.B. via auth.admin-
18
- // seed), skip diese Migration. Verhindert Konflikt beim re-boot wenn
19
- // jemand den Marker manuell gelöscht hat.
17
+ // Idempotency check: if tenants already exist (e.g. via an auth.admin
18
+ // seed), skip this migration. Prevents a conflict on re-boot if
19
+ // someone deleted the marker manually.
20
20
  const existing = await ctx.findTenants();
21
21
  if (existing.some((t) => t.tenantKey === "alice-corp")) return;
22
22
 
@@ -1,11 +1,11 @@
1
- // Driver-Pattern: ergänze fehlende Rolle auf existierenden Memberships.
2
- // Spiegelt den realen publicstatus-Bug 2026-05-20 (admin@publicstatus.eu
3
- // hatte "Admin" aber nicht "TenantAdmin" → text-content + legal-pages
4
- // access_denied weil bundled-features TenantAdmin erwarten).
1
+ // Driver pattern: add a missing role to existing memberships.
2
+ // Mirrors the real publicstatus bug of 2026-05-20 (admin@publicstatus.eu
3
+ // had "Admin" but not "TenantAdmin" → text-content + legal-pages
4
+ // access_denied because bundled-features expect TenantAdmin).
5
5
  //
6
- // Funktioniert für jede App mit dem gleichen Pattern: User-Lookup per
7
- // E-Mail → alle Memberships durchgehen → bei Bedarf via existierendem
8
- // updateMemberRoles-Handler korrigieren.
6
+ // Works for any app with the same pattern: user lookup by
7
+ // email → go through all memberships → correct via the existing
8
+ // updateMemberRoles handler if needed.
9
9
 
10
10
  import type { SeedMigration } from "@cosmicdrift/kumiko-framework/es-ops";
11
11
 
@@ -25,12 +25,12 @@ export default {
25
25
  tenantId: m.tenantId,
26
26
  roles: Array.from(new Set([...m.roles, "TenantAdmin"])),
27
27
  },
28
- // tenantIdOverride: stream-tenant aus kumiko_events.v1 — NICHT
29
- // m.tenantId (payload-tenant). Beide stimmen oft überein, weichen
30
- // aber ab wenn das Aggregate ursprünglich von einem fremden
31
- // Executor angelegt wurde (seedTenantMembership by=systemAdmin —
32
- // publicstatus-Pattern). findMembershipsOfUser liefert beide,
33
- // kein eigener JOIN nötig.
28
+ // tenantIdOverride: stream tenant from kumiko_events.v1 — NOT
29
+ // m.tenantId (payload tenant). The two often match, but differ
30
+ // when the aggregate was originally created by a foreign
31
+ // executor (seedTenantMembership by=systemAdmin —
32
+ // publicstatus pattern). findMembershipsOfUser returns both,
33
+ // no separate JOIN needed.
34
34
  m.streamTenantId,
35
35
  );
36
36
  }
@@ -1,20 +1,20 @@
1
- // User-Data-Rights — Recipe
1
+ // User-Data-Rights — recipe
2
2
  //
3
- // Wie eine App-Domain DSGVO-Pipeline integriert: pro Entity einen
4
- // (export, delete)-Hook ueber EXT_USER_DATA. Forget-Cron, Export-ZIP-Bau
5
- // und Magic-Link-Versand kommen vollstaendig aus user-data-rights —
6
- // App-Author schreibt nur die zwei Hooks pro Entity.
3
+ // How an app domain integrates the GDPR pipeline: one
4
+ // (export, delete) hook per entity via EXT_USER_DATA. Forget cron, export ZIP build
5
+ // and magic link delivery come entirely from user-data-rights —
6
+ // the app author writes only the two hooks per entity.
7
7
  //
8
- // Demo-Domain: minimaler Note-Service. Note hat (id, tenantId, authorId,
9
- // title, body). Pinst:
10
- // 1. Hook export liefert Note-Rows als JSON-Snippet ins Export-Bundle.
11
- // 2. Hook delete entfernt Note-Rows beim Forget-Cleanup-Cron.
8
+ // Demo domain: minimal note service. A note has (id, tenantId, authorId,
9
+ // title, body). Pins:
10
+ // 1. The export hook delivers note rows as a JSON snippet into the export bundle.
11
+ // 2. The delete hook removes note rows during the forget-cleanup cron.
12
12
  //
13
- // Was nicht im Recipe ist (siehe samples/apps/user-data-rights-demo
14
- // fuer eine vollstaendige App):
13
+ // What is not in the recipe (see samples/apps/user-data-rights-demo
14
+ // for a complete app):
15
15
  // - Strategy-aware delete (anonymize vs hardDelete)
16
- // - HTTP-Endpoints fuer create/list
17
- // - Compliance-Profile-Wiring + Cron-Scheduling
16
+ // - HTTP endpoints for create/list
17
+ // - Compliance profile wiring + cron scheduling
18
18
 
19
19
  import {
20
20
  deleteMany,