@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.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +1 -1
- package/packages/bundled-features/src/admin-shell/changes.json +6 -0
- package/packages/bundled-features/src/admin-shell/feature.ts +60 -31
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +2 -2
- package/packages/bundled-features/src/cap-overview/changes.json +18 -0
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -0
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +3 -2
- package/packages/bundled-features/src/cap-overview/i18n.ts +4 -0
- package/packages/bundled-features/src/cap-overview/types.ts +4 -1
- package/packages/bundled-features/src/cap-overview/web/cap-amount-format.ts +37 -0
- package/packages/bundled-features/src/cap-overview/web/cap-cards-panel.tsx +137 -54
- package/packages/bundled-features/src/cap-overview/web/cap-usage-bar.tsx +18 -4
- package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -3
- package/packages/bundled-features/src/cap-overview/web/usage-math.ts +5 -1
- package/packages/bundled-features/src/channel-email/app-transport.ts +37 -0
- package/packages/bundled-features/src/channel-email/changes.json +6 -0
- package/packages/bundled-features/src/channel-email/feature.ts +6 -0
- package/packages/bundled-features/src/channel-email/index.ts +5 -0
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +2 -22
- package/packages/bundled-features/src/foundation-shared/changes.json +6 -0
- package/packages/bundled-features/src/foundation-shared/index.ts +1 -0
- package/packages/bundled-features/src/foundation-shared/mail-display-name.ts +20 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-record.ts +48 -0
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +18 -0
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +17 -0
- package/packages/bundled-features/src/inbound-mail-foundation/index.ts +18 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-mail.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/probe.ts +8 -45
- package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +14 -0
- package/packages/bundled-features/src/inbound-mail-foundation/send-from-account.ts +118 -0
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +8 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +13 -8
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +18 -0
- package/packages/bundled-features/src/inbound-provider-imap/connect-mailbox.ts +158 -0
- package/packages/bundled-features/src/inbound-provider-imap/constants.ts +44 -0
- package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +47 -1
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +12 -35
- package/packages/bundled-features/src/inbound-provider-imap/i18n.ts +49 -0
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/screens.ts +52 -0
- package/packages/bundled-features/src/inbound-provider-imap/send-mail.ts +73 -0
- package/packages/bundled-features/src/inbound-provider-imap/sent-folder.ts +49 -0
- package/packages/bundled-features/src/inbound-provider-imap/smtp-client.ts +135 -0
- package/packages/bundled-features/src/legal-pages/README.md +5 -10
- package/packages/bundled-features/src/mail-foundation/changes.json +6 -0
- package/packages/bundled-features/src/mail-foundation/i18n.ts +9 -1
- package/packages/bundled-features/src/mail-foundation/probe.ts +43 -22
- package/packages/bundled-features/src/readiness/built-in-probes.ts +15 -2
- package/packages/bundled-features/src/readiness/changes.json +30 -0
- package/packages/bundled-features/src/readiness/constants.ts +2 -0
- package/packages/bundled-features/src/readiness/i18n.ts +7 -1
- package/packages/bundled-features/src/readiness/index.ts +6 -1
- package/packages/bundled-features/src/readiness/screens.ts +25 -2
- package/packages/bundled-features/src/readiness/visible-results.ts +2 -1
- package/packages/bundled-features/src/renderer-foundation/README.md +19 -21
- package/packages/bundled-features/src/template-resolver/README.md +144 -165
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/feature.ts +17 -6
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +3 -3
- package/packages/bundled-features/src/user-data-rights/schema/download-token.ts +5 -3
- package/packages/bundled-features/src/user-data-rights/token-helpers.ts +5 -4
- package/samples/apps/cap-billing-demo/bin/server.ts +7 -7
- package/samples/apps/cap-billing-demo/src/feature.ts +38 -38
- package/samples/apps/cap-billing-demo/src/run-config.ts +20 -20
- package/samples/apps/cap-billing-demo/src/tier-map.ts +14 -14
- package/samples/apps/config-demo/src/app/server.ts +4 -4
- package/samples/apps/config-demo/src/features/demo/feature.ts +4 -4
- package/samples/apps/marketing-demo/playwright.config.ts +3 -3
- package/samples/apps/marketing-demo/src/app/client.tsx +2 -2
- package/samples/apps/marketing-demo/src/app/seed-data.ts +9 -9
- package/samples/apps/marketing-demo/src/app/seed.ts +14 -14
- package/samples/apps/marketing-demo/src/app/server.ts +7 -7
- package/samples/apps/marketing-demo/src/app/shell.tsx +3 -3
- package/samples/apps/marketing-demo/src/features/assets/feature.ts +3 -3
- package/samples/apps/marketing-demo/src/features/assets/i18n.ts +7 -7
- package/samples/apps/marketing-demo/src/features/assets/web/index.ts +2 -2
- package/samples/apps/showcase/src/app/client.tsx +3 -3
- package/samples/apps/showcase/src/app/seed-items.ts +14 -14
- package/samples/apps/showcase/src/app/server.ts +7 -7
- package/samples/apps/showcase/src/features/demos/feature.ts +13 -13
- package/samples/apps/showcase/src/features/demos/i18n.ts +2 -2
- package/samples/apps/showcase/src/features/demos/web/components/page.tsx +5 -5
- package/samples/apps/showcase/src/features/demos/web/index.ts +4 -4
- package/samples/apps/showcase/src/features/demos/web/pages/dialog.tsx +1 -1
- package/samples/apps/showcase/src/features/demos/web/pages/inputs.tsx +8 -8
- package/samples/apps/showcase/src/features/demos/web/pages/layout.tsx +5 -5
- package/samples/apps/showcase/src/features/demos/web/pages/toast.tsx +3 -3
- package/samples/apps/showcase/src/features/items/feature.ts +8 -8
- package/samples/apps/showcase/src/features/items/i18n.ts +3 -3
- package/samples/apps/showcase/src/features/items/index.ts +3 -3
- package/samples/apps/showcase/src/features/items/schema/index.ts +4 -4
- package/samples/apps/showcase/src/features/items/schema/item.ts +27 -27
- package/samples/apps/showcase/src/features/items/web/index.ts +6 -6
- package/samples/apps/styleguide/src/app/seed.ts +3 -3
- package/samples/apps/styleguide/src/app/server.ts +2 -2
- package/samples/apps/styleguide/src/app/shell.tsx +4 -4
- package/samples/apps/styleguide/src/features/content/web.tsx +6 -6
- package/samples/apps/styleguide/src/features/examples/feature.ts +8 -8
- package/samples/apps/styleguide/src/features/gallery/feature.ts +9 -9
- package/samples/apps/styleguide/src/features/gallery/web/Gallery.tsx +12 -12
- package/samples/apps/styleguide/src/features/widgets/feature.ts +2 -2
- package/samples/apps/styleguide/src/features/widgets/web/DashboardFilterEcho.tsx +3 -3
- package/samples/apps/styleguide/src/features/widgets/web/DashboardKpiIcon.tsx +4 -4
- package/samples/apps/ui-walkthrough/playwright.config.ts +6 -6
- package/samples/apps/ui-walkthrough/scripts/emit-e2e-data.ts +5 -5
- package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +13 -13
- package/samples/apps/ui-walkthrough/src/app/client.tsx +2 -2
- package/samples/apps/ui-walkthrough/src/app/server.ts +14 -14
- package/samples/apps/ui-walkthrough/src/app/shell.tsx +3 -3
- package/samples/apps/use-all-bundled/bin/main.ts +5 -5
- package/samples/apps/use-all-bundled/feature-manifest.json +10 -4
- package/samples/apps/use-all-bundled/kumiko/schema.ts +7 -7
- package/samples/apps/use-all-bundled/scripts/check-coverage.ts +20 -20
- package/samples/apps/use-all-bundled/src/app/auth-constants.ts +4 -4
- package/samples/apps/use-all-bundled/src/app/client.tsx +4 -4
- package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +12 -12
- package/samples/apps/use-all-bundled/src/app/seed.ts +6 -6
- package/samples/apps/user-data-rights-demo/src/feature.ts +23 -23
- package/samples/apps/workspaces/playwright.config.ts +5 -5
- package/samples/apps/workspaces/src/app/auth-constants.ts +3 -3
- package/samples/apps/workspaces/src/app/server.ts +16 -16
- package/samples/apps/workspaces/src/app/shell.tsx +2 -2
- package/samples/apps/workspaces/src/features/demo/schema/order.ts +4 -4
- package/samples/apps/workspaces/src/features/demo-driver/feature.ts +5 -5
- package/samples/recipes/apex-surface-auth/src/feature.ts +31 -31
- package/samples/recipes/designer-demo/src/feature.ts +6 -6
- package/samples/recipes/encrypted-tenant-config/src/feature.ts +20 -20
- package/samples/recipes/lane-routing/src/feature.ts +5 -5
- package/samples/recipes/legal-pages/src/feature.ts +29 -29
- package/samples/recipes/record-detail-layout/src/feature.ts +21 -2
- package/samples/recipes/seed-migration/scripts/smoke.ts +20 -20
- package/samples/recipes/seed-migration/seeds/2026-01-01-initial-tenants.ts +11 -11
- package/samples/recipes/seed-migration/seeds/2026-02-01-fix-admin-roles.ts +13 -13
- package/samples/recipes/user-data-rights/src/feature.ts +13 -13
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
// Local dev
|
|
1
|
+
// Local dev server for the cap-billing-demo sample.
|
|
2
2
|
//
|
|
3
|
-
// Demo
|
|
4
|
-
// 100/month). Mails
|
|
5
|
-
//
|
|
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
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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 —
|
|
4
|
-
// mail-foundation
|
|
3
|
+
// newsletter — demo feature that brings together tier-engine + cap-counter + the
|
|
4
|
+
// mail-foundation plugin API.
|
|
5
5
|
//
|
|
6
|
-
// **
|
|
7
|
-
// 1. Pre-
|
|
8
|
-
// a)
|
|
9
|
-
// b)
|
|
10
|
-
// c) enforceCapAndMaybeNotify
|
|
11
|
-
// soft-hit
|
|
12
|
-
//
|
|
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
|
|
15
|
-
// 3. Post-
|
|
14
|
+
// 2. Inner handler: createTransportForTenant → transport.send
|
|
15
|
+
// 3. Post-success: increment the cap counter by 1
|
|
16
16
|
//
|
|
17
|
-
// **
|
|
18
|
-
// -
|
|
19
|
-
// -
|
|
20
|
-
//
|
|
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
|
|
23
|
-
// billing-foundation (=
|
|
24
|
-
//
|
|
25
|
-
// "newsletter:config:tier" —
|
|
26
|
-
//
|
|
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
|
-
*
|
|
106
|
+
* Resolve the tenant tier.
|
|
107
107
|
*
|
|
108
|
-
* **
|
|
109
|
-
* 1. subscription
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* 2. config
|
|
114
|
-
*
|
|
115
|
-
*
|
|
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
|
-
*
|
|
119
|
-
*
|
|
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
|
|
147
|
-
*
|
|
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
|
|
198
|
-
// cap-counter (
|
|
199
|
-
//
|
|
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
|
|
220
|
-
*
|
|
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
|
|
1
|
+
// Single source of truth for the feature composition of the demo app.
|
|
2
2
|
//
|
|
3
|
-
// **Mount
|
|
4
|
-
// inmemory
|
|
5
|
-
// newsletter
|
|
6
|
-
// cap-counter). composeFeatures (auth
|
|
7
|
-
// auth-email-password
|
|
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
|
-
// **
|
|
10
|
-
// - cap-counter —
|
|
11
|
-
// - mail-foundation —
|
|
12
|
-
// - mail-transport-inmemory —
|
|
13
|
-
// - billing-foundation —
|
|
14
|
-
// (Stripe/Mollie).
|
|
15
|
-
//
|
|
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
|
-
//
|
|
19
|
-
// - newsletter —
|
|
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
|
|
32
|
+
* Demo app features.
|
|
33
33
|
*
|
|
34
|
-
* **createSecretsFeature()
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
|
2
|
-
//
|
|
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
|
|
5
|
-
// foundation + cap-counter + newsletter).
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
|
14
|
-
* - newslettersPerMonth:
|
|
15
|
-
* - aiSummariesPer7Days:
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
-
//
|
|
29
|
-
// 11× → soft-hit, 12× → hard-block.
|
|
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
|
|
4
|
-
// →
|
|
5
|
-
// (samples/apps/<x>/src/app/server.ts)
|
|
6
|
-
//
|
|
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-
|
|
35
|
-
//
|
|
36
|
-
// (
|
|
37
|
-
// mask.title
|
|
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
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
|
2
|
-
// shared shell,
|
|
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
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
|
7
|
-
// AssetDepartment, etc.) —
|
|
8
|
-
//
|
|
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
|
-
//
|
|
306
|
-
//
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// (assets:write:asset:create, helpdesk:write:ticket:create)
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
9
|
-
//
|
|
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
|
|
27
|
-
//
|
|
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
|
-
//
|
|
107
|
-
//
|
|
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
|
|
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
|
|
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
|
|
4
|
-
// JWT (TestUsers.admin)
|
|
5
|
-
//
|
|
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
|
-
//
|
|
8
|
-
//
|
|
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
|
|
19
|
-
//
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
|
2
|
-
// + `entity:asset:field:<name>`
|
|
3
|
-
// `screen:<id>.title`
|
|
4
|
-
//
|
|
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:
|
|
7
|
-
// `assets:entity:asset:field:<field>:option:<value>`
|
|
8
|
-
//
|
|
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.
|
|
2
|
-
//
|
|
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
|
|
2
|
-
//
|
|
3
|
-
// createKumikoApp.
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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:
|
|
9
|
-
//
|
|
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
|
-
//
|
|
17
|
-
//
|
|
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
|
|
87
|
-
// schema-builder buildInsertSchema). dialect.toDriver()
|
|
88
|
-
//
|
|
89
|
-
//
|
|
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
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
|
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
|
|
28
|
-
// Idempotent — re-running
|
|
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
|
});
|