@cosmicdrift/kumiko-samples 0.391.1 → 0.392.1

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 (137) 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 +18 -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 +137 -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 +18 -0
  37. package/packages/bundled-features/src/inbound-provider-imap/connect-mailbox.ts +158 -0
  38. package/packages/bundled-features/src/inbound-provider-imap/constants.ts +44 -0
  39. package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +47 -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 +49 -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 +52 -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 +135 -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/samples/apps/cap-billing-demo/bin/server.ts +7 -7
  66. package/samples/apps/cap-billing-demo/src/feature.ts +38 -38
  67. package/samples/apps/cap-billing-demo/src/run-config.ts +20 -20
  68. package/samples/apps/cap-billing-demo/src/tier-map.ts +14 -14
  69. package/samples/apps/config-demo/src/app/server.ts +4 -4
  70. package/samples/apps/config-demo/src/features/demo/feature.ts +4 -4
  71. package/samples/apps/marketing-demo/playwright.config.ts +3 -3
  72. package/samples/apps/marketing-demo/src/app/client.tsx +2 -2
  73. package/samples/apps/marketing-demo/src/app/seed-data.ts +9 -9
  74. package/samples/apps/marketing-demo/src/app/seed.ts +14 -14
  75. package/samples/apps/marketing-demo/src/app/server.ts +7 -7
  76. package/samples/apps/marketing-demo/src/app/shell.tsx +3 -3
  77. package/samples/apps/marketing-demo/src/features/assets/feature.ts +3 -3
  78. package/samples/apps/marketing-demo/src/features/assets/i18n.ts +7 -7
  79. package/samples/apps/marketing-demo/src/features/assets/web/index.ts +2 -2
  80. package/samples/apps/showcase/src/app/client.tsx +3 -3
  81. package/samples/apps/showcase/src/app/seed-items.ts +14 -14
  82. package/samples/apps/showcase/src/app/server.ts +7 -7
  83. package/samples/apps/showcase/src/features/demos/feature.ts +13 -13
  84. package/samples/apps/showcase/src/features/demos/i18n.ts +2 -2
  85. package/samples/apps/showcase/src/features/demos/web/components/page.tsx +5 -5
  86. package/samples/apps/showcase/src/features/demos/web/index.ts +4 -4
  87. package/samples/apps/showcase/src/features/demos/web/pages/dialog.tsx +1 -1
  88. package/samples/apps/showcase/src/features/demos/web/pages/inputs.tsx +8 -8
  89. package/samples/apps/showcase/src/features/demos/web/pages/layout.tsx +5 -5
  90. package/samples/apps/showcase/src/features/demos/web/pages/toast.tsx +3 -3
  91. package/samples/apps/showcase/src/features/items/feature.ts +8 -8
  92. package/samples/apps/showcase/src/features/items/i18n.ts +3 -3
  93. package/samples/apps/showcase/src/features/items/index.ts +3 -3
  94. package/samples/apps/showcase/src/features/items/schema/index.ts +4 -4
  95. package/samples/apps/showcase/src/features/items/schema/item.ts +27 -27
  96. package/samples/apps/showcase/src/features/items/web/index.ts +6 -6
  97. package/samples/apps/styleguide/src/app/seed.ts +3 -3
  98. package/samples/apps/styleguide/src/app/server.ts +2 -2
  99. package/samples/apps/styleguide/src/app/shell.tsx +4 -4
  100. package/samples/apps/styleguide/src/features/content/web.tsx +6 -6
  101. package/samples/apps/styleguide/src/features/examples/feature.ts +8 -8
  102. package/samples/apps/styleguide/src/features/gallery/feature.ts +9 -9
  103. package/samples/apps/styleguide/src/features/gallery/web/Gallery.tsx +12 -12
  104. package/samples/apps/styleguide/src/features/widgets/feature.ts +2 -2
  105. package/samples/apps/styleguide/src/features/widgets/web/DashboardFilterEcho.tsx +3 -3
  106. package/samples/apps/styleguide/src/features/widgets/web/DashboardKpiIcon.tsx +4 -4
  107. package/samples/apps/ui-walkthrough/playwright.config.ts +6 -6
  108. package/samples/apps/ui-walkthrough/scripts/emit-e2e-data.ts +5 -5
  109. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +13 -13
  110. package/samples/apps/ui-walkthrough/src/app/client.tsx +2 -2
  111. package/samples/apps/ui-walkthrough/src/app/server.ts +14 -14
  112. package/samples/apps/ui-walkthrough/src/app/shell.tsx +3 -3
  113. package/samples/apps/use-all-bundled/bin/main.ts +5 -5
  114. package/samples/apps/use-all-bundled/feature-manifest.json +10 -4
  115. package/samples/apps/use-all-bundled/kumiko/schema.ts +7 -7
  116. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +20 -20
  117. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +4 -4
  118. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -4
  119. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +12 -12
  120. package/samples/apps/use-all-bundled/src/app/seed.ts +6 -6
  121. package/samples/apps/user-data-rights-demo/src/feature.ts +23 -23
  122. package/samples/apps/workspaces/playwright.config.ts +5 -5
  123. package/samples/apps/workspaces/src/app/auth-constants.ts +3 -3
  124. package/samples/apps/workspaces/src/app/server.ts +16 -16
  125. package/samples/apps/workspaces/src/app/shell.tsx +2 -2
  126. package/samples/apps/workspaces/src/features/demo/schema/order.ts +4 -4
  127. package/samples/apps/workspaces/src/features/demo-driver/feature.ts +5 -5
  128. package/samples/recipes/apex-surface-auth/src/feature.ts +31 -31
  129. package/samples/recipes/designer-demo/src/feature.ts +6 -6
  130. package/samples/recipes/encrypted-tenant-config/src/feature.ts +20 -20
  131. package/samples/recipes/lane-routing/src/feature.ts +5 -5
  132. package/samples/recipes/legal-pages/src/feature.ts +29 -29
  133. package/samples/recipes/record-detail-layout/src/feature.ts +21 -2
  134. package/samples/recipes/seed-migration/scripts/smoke.ts +20 -20
  135. package/samples/recipes/seed-migration/seeds/2026-01-01-initial-tenants.ts +11 -11
  136. package/samples/recipes/seed-migration/seeds/2026-02-01-fix-admin-roles.ts +13 -13
  137. package/samples/recipes/user-data-rights/src/feature.ts +13 -13
@@ -1,12 +1,12 @@
1
- // Local dev-server für das cap-billing-demo Sample.
1
+ // Local dev server for the cap-billing-demo sample.
2
2
  //
3
- // Demo-Story: Newsletter-Send mit per-Tier-Cap (free=10/month, pro=
4
- // 100/month). Mails landen im in-memory-transport, abrufbar über die
5
- // Helper aus @cosmicdrift/kumiko-bundled-features/mail-transport-inmemory.
3
+ // Demo story: newsletter send with a per-tier cap (free=10/month, pro=
4
+ // 100/month). Mails land in the in-memory transport, retrievable via the
5
+ // helpers from @cosmicdrift/kumiko-bundled-features/mail-transport-inmemory.
6
6
  //
7
- // Vorbedingungen (siehe README): Postgres + Redis via `bun kumiko dev`;
8
- // Migrations via `bun kumiko migrate apply` (aktuell nur die Default-
9
- // Tabellen — das Demo nutzt keine eigene Entity-Projektion).
7
+ // Prerequisites (see README): Postgres + Redis via `bun kumiko dev`;
8
+ // migrations via `bun kumiko migrate apply` (currently only the default
9
+ // tables — the demo has no entity projection of its own).
10
10
 
11
11
  import { runDevApp } from "@cosmicdrift/kumiko-dev-server";
12
12
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
@@ -1,29 +1,29 @@
1
1
  // kumiko-feature-version: 1
2
2
  //
3
- // newsletter — Demo-Feature, das tier-engine + cap-counter + die
4
- // mail-foundation Plugin-API zusammenbringt.
3
+ // newsletter — demo feature that brings together tier-engine + cap-counter + the
4
+ // mail-foundation plugin API.
5
5
  //
6
- // **Was passiert beim send-Handler:**
7
- // 1. Pre-Call (via withCapEnforcement-Wrapper):
8
- // a) Tenant-Tier aus ctx.config lesen
9
- // b) Tier → cap-limit aus DEMO_TIER_MAP mappen
10
- // c) enforceCapAndMaybeNotify ruft enforceCap → bei
11
- // soft-hit-crossing den Notifier (sendet Warning-Mail über
12
- // dieselbe mail-foundation, an den Admin) UND dispatched
6
+ // **What happens in the send handler:**
7
+ // 1. Pre-call (via the withCapEnforcement wrapper):
8
+ // a) Read the tenant tier from ctx.config
9
+ // b) Map tier → cap limit from DEMO_TIER_MAP
10
+ // c) enforceCapAndMaybeNotify calls enforceCap → on a
11
+ // soft-hit crossing the notifier (sends a warning mail via
12
+ // the same mail-foundation, to the admin) AND dispatches
13
13
  // mark-soft-warned
14
- // 2. Inner Handler: createTransportForTenant → transport.send
15
- // 3. Post-Success: increment-cap-Counter um 1
14
+ // 2. Inner handler: createTransportForTenant → transport.send
15
+ // 3. Post-success: increment the cap counter by 1
16
16
  //
17
- // **Beobachtbar im InMemory-Inbox** (mail-transport-inmemory):
18
- // - Die "echten" Newsletter-Mails (an event.payload.to)
19
- // - Die Soft-Hit-Warning-Mails (an admin@tenant.demo, beim ersten
20
- // überschreiten)
17
+ // **Observable in the in-memory inbox** (mail-transport-inmemory):
18
+ // - The "real" newsletter mails (to event.payload.to)
19
+ // - The soft-hit warning mails (to admin@tenant.demo, on the first
20
+ // crossing)
21
21
  //
22
- // **Tier-Switching:** primary-source ist die `subscription`-row aus
23
- // billing-foundation (= produktiver Pfad: Stripe/Mollie webhook
24
- // schreibt → tier ändert sich). Fallback ist der config-key
25
- // "newsletter:config:tier" — den nutzt die Demo-README für manuelles
26
- // Switchen ohne Provider, plus tier-engine-only-Tests behalten so ihren
22
+ // **Tier switching:** the primary source is the `subscription` row from
23
+ // billing-foundation (= productive path: a Stripe/Mollie webhook
24
+ // writes → the tier changes). The fallback is the config key
25
+ // "newsletter:config:tier" — the demo README uses it for manual
26
+ // switching without a provider, and tier-engine-only tests keep their
27
27
  // existing flow.
28
28
 
29
29
  import {
@@ -103,20 +103,20 @@ const innerSendHandler: WriteHandlerDef = {
103
103
  // =============================================================================
104
104
 
105
105
  /**
106
- * Tenant-Tier auflösen.
106
+ * Resolve the tenant tier.
107
107
  *
108
- * **Reihenfolge:**
109
- * 1. subscription-row aus billing-foundation (= produktiver
110
- * Pfad). Wenn die row existiert, trumpft sie den config-fallback —
111
- * auch im canceled-Status (= Tenant fällt auf free zurück, NICHT
112
- * auf einen verwaisten config="pro"-override).
113
- * 2. config-key "newsletter:config:tier" (= Demo-README-Pfad ohne
114
- * Provider; auch der tier-engine-only-Pfad der ursprünglichen
115
- * Tests bleibt so grün).
108
+ * **Order:**
109
+ * 1. subscription row from billing-foundation (= productive
110
+ * path). If the row exists, it beats the config fallback —
111
+ * even in canceled status (= the tenant falls back to free, NOT
112
+ * to an orphaned config="pro" override).
113
+ * 2. config key "newsletter:config:tier" (= demo README path without
114
+ * a provider; the tier-engine-only path of the original
115
+ * tests stays green too).
116
116
  * 3. Default "free".
117
117
  *
118
- * Whitelist-Filter via TIER_NAMES verhindert Tippos ("Pro" / "Premium"),
119
- * die sonst silent zu free fallen würden.
118
+ * The whitelist filter via TIER_NAMES prevents typos ("Pro" / "Premium")
119
+ * that would otherwise silently fall back to free.
120
120
  */
121
121
  function isValidTierName(value: string): value is TierName {
122
122
  return (TIER_NAMES as readonly string[]).includes(value);
@@ -143,8 +143,8 @@ async function resolveTier(ctx: ReservationContext): Promise<TierName> {
143
143
  return "free";
144
144
  }
145
145
 
146
- /** Notifier-Factory: bauen einen SoftHitNotifier der die Warnung über
147
- * dieselbe mail-foundation an den Tenant-Admin schickt. */
146
+ /** Notifier factory: builds a SoftHitNotifier that sends the warning via
147
+ * the same mail-foundation to the tenant admin. */
148
148
  function buildSoftHitNotifier(ctx: ReservationContext): SoftHitNotifier {
149
149
  return async (info) => {
150
150
  const transport = await createTransportForTenant(
@@ -194,9 +194,9 @@ const wrappedSendHandler = withCapEnforcement(innerSendHandler, async (_event, c
194
194
  // =============================================================================
195
195
 
196
196
  export const newsletterFeature = defineFeature(FEATURE_NAME, (r) => {
197
- // Hard-deps: mail-foundation für Plugin-API + config für tier-Wahl +
198
- // cap-counter (transitiv via withCapEnforcement, aber explicit ist
199
- // klarer für boot-Validator-Errors).
197
+ // Hard deps: mail-foundation for the plugin API + config for the tier choice +
198
+ // cap-counter (transitive via withCapEnforcement, but explicit is
199
+ // clearer for boot-validator errors).
200
200
  r.requires({ features: ["config"] });
201
201
  r.requires({ features: ["cap-counter"] });
202
202
  r.requires({ features: [createMailFoundationFeature().name] });
@@ -216,7 +216,7 @@ export const newsletterFeature = defineFeature(FEATURE_NAME, (r) => {
216
216
  r.writeHandler(wrappedSendHandler);
217
217
  });
218
218
 
219
- /** QN für den send-Handler — exportiert damit Tests + Clients ohne
220
- * Hand-Ableitung zugreifen. */
219
+ /** QN for the send handler — exported so tests + clients can access it
220
+ * without deriving it by hand. */
221
221
  export const NEWSLETTER_SEND_QN = "newsletter:write:send";
222
222
  export const NEWSLETTER_TIER_CONFIG_KEY = "newsletter:config:tier";
@@ -1,22 +1,22 @@
1
- // Single source of truth für die Feature-Komposition der Demo-App.
1
+ // Single source of truth for the feature composition of the demo app.
2
2
  //
3
- // **Mount-Order matters:** mail-foundation MUSS vor mail-transport-
4
- // inmemory stehen (`r.requires({ features: ["mail-foundation"] })` im Plugin); das
5
- // newsletter-feature kommt danach (es nutzt mail-foundation +
6
- // cap-counter). composeFeatures (auth-mode) ergänzt config + secrets +
7
- // auth-email-password automatisch.
3
+ // **Mount order matters:** mail-foundation MUST come before mail-transport-
4
+ // inmemory (`r.requires({ features: ["mail-foundation"] })` in the plugin); the
5
+ // newsletter feature comes after (it uses mail-foundation +
6
+ // cap-counter). composeFeatures (auth mode) adds config + secrets +
7
+ // auth-email-password automatically.
8
8
  //
9
- // **Was hier gemountet wird:**
10
- // - cap-counter — Counter-Storage + Helpers
11
- // - mail-foundation — Plugin-Host für Transports
12
- // - mail-transport-inmemory — Demo-Transport (kein SMTP nötig)
13
- // - billing-foundation — Plugin-Host für Subscription-Provider
14
- // (Stripe/Mollie). Demo mountet Provider
15
- // nicht selbst — App-Owner ergänzt
9
+ // **What is mounted here:**
10
+ // - cap-counter — counter storage + helpers
11
+ // - mail-foundation — plugin host for transports
12
+ // - mail-transport-inmemory — demo transport (no SMTP needed)
13
+ // - billing-foundation — plugin host for subscription providers
14
+ // (Stripe/Mollie). The demo does not mount a provider
15
+ // itself — the app owner adds
16
16
  // createSubscriptionStripeFeature(...) /
17
17
  // createSubscriptionMollieFeature(...) in
18
- // der eigenen run-config.
19
- // - newsletter — die Demo-Domain mit cap-aware send
18
+ // their own run-config.
19
+ // - newsletter — the demo domain with cap-aware send
20
20
 
21
21
  import { createBillingFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
22
22
  import { createCapCounterFeature } from "@cosmicdrift/kumiko-bundled-features/cap-counter";
@@ -29,12 +29,12 @@ import { localeDe } from "@cosmicdrift/kumiko-locale-de";
29
29
  import { newsletterFeature } from "./feature";
30
30
 
31
31
  /**
32
- * Demo-App-Features.
32
+ * Demo app features.
33
33
  *
34
- * **createSecretsFeature() Single-Instance:** wird EINMAL beim
35
- * Module-Load aufgerufen. Konsumenten die `run-config.ts` mehrfach
36
- * importieren (bin/server.ts + Tests) kriegen via ESM-Module-Cache
37
- * dasselbe Feature-Objekt zurück.
34
+ * **createSecretsFeature() single instance:** is called ONCE at
35
+ * module load. Consumers that import `run-config.ts` several times
36
+ * (bin/server.ts + tests) get the same feature object back via the
37
+ * ESM module cache.
38
38
  */
39
39
  export const APP_FEATURES = [
40
40
  localeDe(),
@@ -1,20 +1,20 @@
1
- // Tier-Map fuer das cap-billing-demo. Source-of-Truth fuer welcher
2
- // Tier wieviel darf — die feature.ts liest das hier zur Cap-Auflösung.
1
+ // Tier map for the cap-billing-demo. Source of truth for which
2
+ // tier may do how much — feature.ts reads it here for cap resolution.
3
3
  //
4
- // **Design-Wahl:** alle Tiers mounten dieselben Features (mail-
5
- // foundation + cap-counter + newsletter). Der Tier-Unterschied steckt
6
- // AUSSCHLIESSLICH in den Caps. So sehen Demo-User: gleicher Code-Pfad,
7
- // unterschiedliche Limits — exakt das Pattern, das eine echte Plattform
8
- // zwischen Free und Pro fährt.
4
+ // **Design choice:** all tiers mount the same features (mail-
5
+ // foundation + cap-counter + newsletter). The tier difference lives
6
+ // EXCLUSIVELY in the caps. So demo users see: same code path,
7
+ // different limits — exactly the pattern a real platform
8
+ // runs between Free and Pro.
9
9
 
10
10
  import type { TierMap } from "@cosmicdrift/kumiko-bundled-features/tier-engine";
11
11
 
12
12
  /**
13
- * Cap-Shape der Demo-App. Zwei Caps:
14
- * - newslettersPerMonth: Calendar-Period-Cap (1.-eines-Monats Reset)
15
- * - aiSummariesPer7Days: Rolling-Window-Cap (Drift-pin: zeigt dass
16
- * beide Cap-Profile parallel funktionieren — auch wenn der
17
- * Demo-Code aiSummaries gerade nicht implementiert)
13
+ * Cap shape of the demo app. Two caps:
14
+ * - newslettersPerMonth: calendar-period cap (reset on the 1st of the month)
15
+ * - aiSummariesPer7Days: rolling-window cap (drift pin: shows that
16
+ * both cap profiles work in parallel — even though the
17
+ * demo code does not implement aiSummaries yet)
18
18
  */
19
19
  export type DemoCaps = {
20
20
  readonly newslettersPerMonth: number;
@@ -25,8 +25,8 @@ export const DEMO_TIER_MAP: TierMap<DemoCaps> = {
25
25
  free: {
26
26
  features: [],
27
27
  caps: {
28
- // Bei limit=10: soft@11 (1.1×), hard@12 (1.2×). Demo-Test feuert
29
- // 11× → soft-hit, 12× → hard-block. Saubere Bands für die Story.
28
+ // At limit=10: soft@11 (1.1×), hard@12 (1.2×). The demo test fires
29
+ // 11× → soft-hit, 12× → hard-block. Clean bands for the story.
30
30
  newslettersPerMonth: 10,
31
31
  aiSummariesPer7Days: 0,
32
32
  },
@@ -1,9 +1,9 @@
1
1
  // @runtime dev
2
2
  //
3
- // Sample-Server für config-demo. Importiert runDevApp aus dev-server
4
- // → muss explizit als [dev]-File markiert sein, weil das Default
5
- // (samples/apps/<x>/src/app/server.ts) ohne directive als [runtime]
6
- // klassifiziert wird und [runtime]→[dev] vom Isolation-Guard blockt.
3
+ // Sample server for config-demo. Imports runDevApp from dev-server
4
+ // → must be explicitly marked as a [dev] file, because the default
5
+ // (samples/apps/<x>/src/app/server.ts) without a directive is classified as [runtime]
6
+ // and [runtime]→[dev] is blocked by the isolation guard.
7
7
 
8
8
  import {
9
9
  createConfigAccessorFactory,
@@ -31,10 +31,10 @@ const REQUIRED_I18N = {
31
31
  "screen:config-demo-user.title": { de: "Persönlich", en: "Personal" },
32
32
  } as const;
33
33
 
34
- // Self-Populating Settings-Hub: jeder Key mit `mask` erscheint automatisch im
35
- // Hub — kein r.screen/r.nav mehr. buildConfigFeatureSchema gruppiert nach Scope
36
- // (Plattform/Organisation/Persönlich) und leitet Felder aus dem Key-Typ ab.
37
- // mask.title ist der i18n-Key des Feld-Labels, mask.order die Reihenfolge.
34
+ // Self-populating settings hub: every key with `mask` appears automatically in the
35
+ // hub — no more r.screen/r.nav. buildConfigFeatureSchema groups by scope
36
+ // (platform/organization/personal) and derives fields from the key type.
37
+ // mask.title is the i18n key of the field label, mask.order the order.
38
38
  export const configDemoFeature = defineFeature("config-demo", (r) => {
39
39
  r.translations({ keys: REQUIRED_I18N });
40
40
  r.requires({ features: ["config"] });
@@ -1,7 +1,7 @@
1
1
  // @runtime test
2
- // Playwright-Config für Marketing-Demo. Generiert Screenshots aus den
3
- // echten gerenderten Pages (Asset-Tracker + Helpdesk) in das konfigurierte
4
- // Screenshot-Verzeichnis.
2
+ // Playwright config for the marketing demo. Generates screenshots from the
3
+ // actually rendered pages (asset tracker + helpdesk) into the configured
4
+ // screenshot directory.
5
5
 
6
6
  import { defineAppE2eConfig, PLAYWRIGHT_DEMO_ENV } from "@cosmicdrift/kumiko-testing/e2e";
7
7
  import { E2E_PORTS } from "../../e2e/e2e-ports";
@@ -1,5 +1,5 @@
1
- // Browser-Entry für Marketing-Demo. Zwei Features (assets, helpdesk),
2
- // shared shell, alles aus dem Framework-Default.
1
+ // Browser entry for the marketing demo. Two features (assets, helpdesk),
2
+ // shared shell, everything from the framework defaults.
3
3
 
4
4
  import { localeDeClient } from "@cosmicdrift/kumiko-locale-de/web";
5
5
  import { createKumikoApp } from "@cosmicdrift/kumiko-renderer/web";
@@ -1,11 +1,11 @@
1
- // Reine Daten für den Marketing-Seed — Templates + Personen-Listen.
2
- // Logik und Random-Erzeugung leben in seed.ts. Trennung weil sich
3
- // Daten häufiger ändern als Logik (mehr Items, andere Namen) und der
4
- // Diff-Lärm den Seed-Algorithmus nicht überlagern soll.
1
+ // Pure data for the marketing seed — templates + people lists.
2
+ // Logic and random generation live in seed.ts. Split because
3
+ // data changes more often than logic (more items, different names) and the
4
+ // diff noise should not drown out the seed algorithm.
5
5
  //
6
- // Type-Felder leiten direkt aus den Schema-Konstanten ab (AssetType,
7
- // AssetDepartment, etc.) — Schema-Änderung an den `options` macht hier
8
- // sofort einen Compile-Error, kein silent drift.
6
+ // Type fields derive directly from the schema constants (AssetType,
7
+ // AssetDepartment, etc.) — a schema change to the `options` causes
8
+ // an immediate compile error here, no silent drift.
9
9
 
10
10
  import type { AssetDepartment, AssetType } from "../features/assets/schema";
11
11
  import type { TicketCategory, TicketDepartment } from "../features/helpdesk/schema";
@@ -302,8 +302,8 @@ export const ASSET_TEMPLATES: ReadonlyArray<AssetTemplate> = [
302
302
  },
303
303
  ];
304
304
 
305
- // Erste Position leer → Math.floor(r() * length) kann „nicht ausgeliehen"
306
- // treffen ohne Sonderfall. Wird nur für status==="lent" konsultiert.
305
+ // First position empty → Math.floor(r() * length) can hit "not lent"
306
+ // without a special case. Only consulted for status==="lent".
307
307
  export const OWNERS: ReadonlyArray<string> = [
308
308
  "",
309
309
  "Alex Morgan",
@@ -1,12 +1,12 @@
1
- // Seed für Marketing-Demo. Plausible Daten — Asset-Tracker mit ~30
2
- // realistischen Items (Laptops, Drucker, Werkzeuge), Helpdesk mit ~20
3
- // echten-aussehenden Tickets. Geht durch den normalen Dispatcher
4
- // (assets:write:asset:create, helpdesk:write:ticket:create) damit alle
5
- // Pipeline-Stufen (Validation, Audit, Search-Index) wie bei echten
6
- // Requests laufen.
1
+ // Seed for the marketing demo. Plausible data — asset tracker with ~30
2
+ // realistic items (laptops, printers, tools), helpdesk with ~20
3
+ // real-looking tickets. Goes through the normal dispatcher
4
+ // (assets:write:asset:create, helpdesk:write:ticket:create) so all
5
+ // pipeline stages (validation, audit, search index) run as with real
6
+ // requests.
7
7
  //
8
- // Daten leben in seed-data.ts (Templates + Personen-Listen). Hier bleibt
9
- // nur Logik: Random-Generator, Datums-Helper, der eigentliche Seed.
8
+ // Data lives in seed-data.ts (templates + people lists). Only logic
9
+ // stays here: random generator, date helper, the actual seed.
10
10
 
11
11
  import type { SeedFn } from "@cosmicdrift/kumiko-dev-server";
12
12
  import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
@@ -23,8 +23,8 @@ import {
23
23
  TICKET_TEMPLATES,
24
24
  } from "./seed-data";
25
25
 
26
- // YYYY-MM-DD-Helper über Temporal.PlainDate — Date-API ist im Repo per
27
- // No-Date-API-Guard verboten.
26
+ // YYYY-MM-DD helper via Temporal.PlainDate — the Date API is banned in the repo by the
27
+ // no-Date-API guard.
28
28
  function formatPlainDate(year: number, month: number, day: number): string {
29
29
  return Temporal.PlainDate.from({ year, month, day }).toString();
30
30
  }
@@ -103,17 +103,17 @@ export const seedMarketingDemo: SeedFn = async (stack) => {
103
103
  const existingTickets = await countTicketsForTenant(stack.db, tenantId);
104
104
  if (existingTickets < 10) {
105
105
  const r = rng(7);
106
- // Anker für dueDate: 2026-05-01 — bewusst konstant, damit Screenshots
107
- // reproduzierbar bleiben (auch wenn der Seed später läuft).
106
+ // Anchor for dueDate: 2026-05-01 — deliberately constant so screenshots
107
+ // stay reproducible (even if the seed runs later).
108
108
  const today = Temporal.PlainDate.from({ year: 2026, month: 5, day: 1 });
109
109
  for (const tpl of TICKET_TEMPLATES) {
110
110
  const severity = pick(TICKET_SEVERITIES, r, "medium");
111
111
  const status = pick(TICKET_STATUSES, r, "open");
112
112
  const reporter = pick(REPORTERS, r, "Anna Weber");
113
113
  const assignee = pick(ASSIGNEES, r, "");
114
- // dueDate: -3 bis +14 Tage — Mix aus überfällig + bald.
114
+ // dueDate: -3 to +14 days — mix of overdue + soon.
115
115
  const dueDate = today.add({ days: Math.floor(r() * 18) - 3 }).toString();
116
- // spentMinutes: 0 für offene, mehr für bearbeitete/gelöste.
116
+ // spentMinutes: 0 for open, more for in-progress/resolved.
117
117
  const spentMinutes =
118
118
  status === "open"
119
119
  ? Math.floor(r() * 15)
@@ -1,11 +1,11 @@
1
1
  // @runtime dev
2
2
  //
3
- // Dev-Server für Marketing-Demo. KEIN Auth — wie showcase, Auto-Mint-
4
- // JWT (TestUsers.admin) damit Marketing-Screenshots ohne Login-Reibung
5
- // gemacht werden können. Cream/Light Theme via styles.css.
3
+ // Dev server for the marketing demo. NO auth — like showcase, auto-mint
4
+ // JWT (TestUsers.admin) so marketing screenshots can be made
5
+ // without login friction. Cream/light theme via styles.css.
6
6
  //
7
- // Zwei Features (Assets + Helpdesk) auf einer Instanz — beweist
8
- // Marketing-Story „mehrere kleine Apps, eine Plattform".
7
+ // Two features (assets + helpdesk) on one instance — proves the
8
+ // marketing story "several small apps, one platform".
9
9
 
10
10
  import { runDevApp } from "@cosmicdrift/kumiko-dev-server";
11
11
  import { localeDe } from "@cosmicdrift/kumiko-locale-de";
@@ -15,8 +15,8 @@ import { seedMarketingDemo } from "./seed";
15
15
 
16
16
  await runDevApp({
17
17
  features: [localeDe(), assetsFeature, helpdeskFeature],
18
- // PORT-env überschreibbar — Playwright e2e nutzt eigenen Port (4179)
19
- // damit dev-mode (4178) parallel laufen kann.
18
+ // PORT env overridable — Playwright e2e uses its own port (4179)
19
+ // so dev mode (4178) can run in parallel.
20
20
  port: Number.parseInt(process.env["PORT"] ?? "4178", 10),
21
21
  clientEntry: "./src/app/client.tsx",
22
22
  htmlPath: "./public/index.html",
@@ -1,6 +1,6 @@
1
- // AppShell für Marketing-Demo. DefaultAppShell mit Brand-Wortmarke
2
- // „Kumiko Demo", Light-Mode-default (kein Theme-Toggle für screenshots
3
- // damit immer der gleiche Look).
1
+ // AppShell for the marketing demo. DefaultAppShell with brand wordmark
2
+ // "Kumiko Demo", light mode default (no theme toggle for screenshots
3
+ // so the look is always the same).
4
4
 
5
5
  import { type AppSchema, DefaultAppShell } from "@cosmicdrift/kumiko-renderer/web";
6
6
  import type { ReactNode } from "react";
@@ -1,6 +1,6 @@
1
- // Assets-Feature — Server-Side. Schema-driven Asset-Tracker für die
2
- // Marketing-Demo. defineFeature registriert Entity + CRUD-Handler +
3
- // Screens. Audit + Multi-Tenant kommen aus dem Framework-Default.
1
+ // Assets feature — server side. Schema-driven asset tracker for the
2
+ // marketing demo. defineFeature registers entity + CRUD handlers +
3
+ // screens. Audit + multi-tenancy come from the framework defaults.
4
4
 
5
5
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
6
6
  import type { TranslationValue } from "@cosmicdrift/kumiko-framework/ui-types";
@@ -1,11 +1,11 @@
1
- // Assets-Feature i18n-Bundle. Convention: feature-prefix `assets:`
2
- // + `entity:asset:field:<name>` für Field-Labels (Spalten + Form),
3
- // `screen:<id>.title` für Page-Titles, `assets:nav.<id>` für
4
- // Sidebar-Labels.
1
+ // Assets feature i18n bundle. Convention: feature prefix `assets:`
2
+ // + `entity:asset:field:<name>` for field labels (columns + form),
3
+ // `screen:<id>.title` for page titles, `assets:nav.<id>` for
4
+ // sidebar labels.
5
5
  //
6
- // Plus: Select-Options bekommen lesbare Labels via
7
- // `assets:entity:asset:field:<field>:option:<value>` damit „lent"
8
- // nicht raw als „lent" im Screenshot landet.
6
+ // Plus: select options get readable labels via
7
+ // `assets:entity:asset:field:<field>:option:<value>` so "lent"
8
+ // does not land raw as "lent" in the screenshot.
9
9
 
10
10
  import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
11
11
 
@@ -1,5 +1,5 @@
1
- // Assets — ClientFeatureDefinition. Pure Schema-driven, keine Custom-
2
- // Components. Render läuft über Standard-RenderList + RenderEdit.
1
+ // Assets — ClientFeatureDefinition. Purely schema-driven, no custom
2
+ // components. Rendering goes through the standard RenderList + RenderEdit.
3
3
 
4
4
  import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer/web";
5
5
  import { assetsTranslations } from "../i18n";
@@ -1,6 +1,6 @@
1
- // Browser-Entry. Dünn — sammelt nur die ClientFeatureDefinitions aus
2
- // den App-Features und übergibt Shell + clientFeatures an
3
- // createKumikoApp. Custom-Screen-Routing macht das Framework selbst
1
+ // Browser entry. Thin — only collects the ClientFeatureDefinitions from
2
+ // the app features and hands shell + clientFeatures to
3
+ // createKumikoApp. The framework does custom-screen routing itself
4
4
  // via clientFeatures.components.
5
5
 
6
6
  import { localeDeClient } from "@cosmicdrift/kumiko-locale-de/web";
@@ -1,20 +1,20 @@
1
- // Seed für die Showcase-Item-Liste — ~200 items damit der Pager 4
2
- // Seiten hat zum Durchklicken (pageSize: 50). Geht durch den normalen
3
- // Dispatcher (showcase:write:item:create) — das hängt sich an alle
4
- // Pipeline-Stufen (Validation, Field-Defaults, Read-Side-Update,
5
- // Search-Index, Audit) genau wie ein echter HTTP-Request, also bricht
6
- // auch nicht wenn das Schema sich ändert (softDelete, neue Felder).
1
+ // Seed for the showcase item list — ~200 items so the pager has 4
2
+ // pages to click through (pageSize: 50). Goes through the normal
3
+ // dispatcher (showcase:write:item:create) — that hooks into all
4
+ // pipeline stages (validation, field defaults, read-side update,
5
+ // search index, audit) just like a real HTTP request, so it does not
6
+ // break when the schema changes (softDelete, new fields).
7
7
  //
8
- // Idempotent: wenn schon ≥100 items existieren, skip — der Server wird
9
- // bei jedem Restart neu geseeded sonst.
8
+ // Idempotent: if ≥100 items already exist, skip — otherwise the server is
9
+ // re-seeded on every restart.
10
10
 
11
11
  import type { SeedFn } from "@cosmicdrift/kumiko-dev-server";
12
12
  import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
13
13
 
14
14
  const STATUSES = ["draft", "active", "blocked", "done"] as const;
15
15
 
16
- // Determinist-Pseudo-Random damit Seeds reproduzierbar sind. Mulberry32
17
- // reicht für Demo-Distribution; kein Crypto, kein "echter" Zufall.
16
+ // Deterministic pseudo-random so seeds are reproducible. Mulberry32
17
+ // is enough for demo distribution; no crypto, no "real" randomness.
18
18
  function mulberry32(seed: number): () => number {
19
19
  let s = seed;
20
20
  return () => {
@@ -83,10 +83,10 @@ export const seedShowcaseItems: SeedFn = async (stack) => {
83
83
  const priority = Math.min(5, Math.floor(rng() * rng() * 6) + 1);
84
84
  const dueOffsetDays = Math.floor(rng() * 90) - 30;
85
85
  const due = now.add({ hours: dueOffsetDays * 24 });
86
- // Zod-Validation für type:"date" verlangt YYYY-MM-DD (siehe
87
- // schema-builder buildInsertSchema). dialect.toDriver() coercd das
88
- // zu start-of-day UTC bevor die DB es sieht — Caller-API bleibt
89
- // wie der Author sie erwartet.
86
+ // Zod validation for type:"date" requires YYYY-MM-DD (see
87
+ // schema-builder buildInsertSchema). dialect.toDriver() coerces that
88
+ // to start-of-day UTC before the DB sees it — the caller API stays
89
+ // as the author expects it.
90
90
  const dueDate = due.toString().slice(0, 10);
91
91
 
92
92
  const res = await stack.http.write(
@@ -1,9 +1,9 @@
1
1
  // @runtime dev
2
2
  //
3
- // Dev-Server für den Showcase. KEIN Auth — der Showcase ist eine UI-
4
- // Primitive-Spielwiese, Login-Reibung ist hier kontraproduktiv. Auto-
5
- // Mint-JWT-Mode aktiv (TestUsers.admin) → direkt im Edit-Screen.
6
- // Wer Auth-Pfade testen will: ui-walkthrough oder workspaces.
3
+ // Dev server for the showcase. NO auth — the showcase is a UI
4
+ // primitive playground, login friction is counterproductive here. Auto-
5
+ // mint JWT mode active (TestUsers.admin) → straight into the edit screen.
6
+ // To test auth paths: ui-walkthrough or workspaces.
7
7
 
8
8
  import { runDevApp } from "@cosmicdrift/kumiko-dev-server";
9
9
  import { localeDe } from "@cosmicdrift/kumiko-locale-de";
@@ -14,7 +14,7 @@ import { seedShowcaseItems } from "./seed-items";
14
14
 
15
15
  await runDevApp({
16
16
  features: [localeDe(), itemsFeature, demosFeature],
17
- // PORT-env überschreibbar — konsistent zu publicstatus / ui-walkthrough.
17
+ // PORT env overridable — consistent with publicstatus / ui-walkthrough.
18
18
  port: Number.parseInt(process.env["PORT"] ?? "4175", 10),
19
19
  clientEntry: "./src/app/client.tsx",
20
20
  htmlPath: "./public/index.html",
@@ -24,7 +24,7 @@ await runDevApp({
24
24
  // existierenden packages/*/src — bei neuen Packages muss man nichts
25
25
  // anpassen.
26
26
  watchDirs: ["./src", "../../../packages/*/src"],
27
- // ~200 Items damit der Pager (pageSize 50) was zum Blättern hat.
28
- // Idempotent — re-running den dev-server seedet nicht doppelt.
27
+ // ~200 items so the pager (pageSize 50) has something to page through.
28
+ // Idempotent — re-running the dev server does not seed twice.
29
29
  seeds: [seedShowcaseItems],
30
30
  });