@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.
- 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 +12 -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 +138 -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 +12 -0
- package/packages/bundled-features/src/inbound-provider-imap/connect-mailbox.ts +156 -0
- package/packages/bundled-features/src/inbound-provider-imap/constants.ts +42 -0
- package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +49 -1
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +12 -35
- package/packages/bundled-features/src/inbound-provider-imap/i18n.ts +48 -0
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/screens.ts +44 -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 +131 -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/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- 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,18 +1,18 @@
|
|
|
1
|
-
// apex-surface-auth
|
|
1
|
+
// apex-surface-auth recipe — the evident way for public apex content.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// schema-
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
9
|
-
// 1. Server:
|
|
10
|
-
// email-
|
|
11
|
-
// 2. Client (
|
|
12
|
-
//
|
|
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
|
|
15
|
-
//
|
|
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:
|
|
28
|
-
// //
|
|
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 —
|
|
38
|
-
// //
|
|
39
|
-
// // (
|
|
40
|
-
// //
|
|
41
|
-
// // auth-mfa, `mfaVerifyScreen: MfaVerifyScreen` (
|
|
42
|
-
// // "@cosmicdrift/kumiko-bundled-features/auth-mfa/web")
|
|
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:
|
|
60
|
-
//
|
|
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
|
|
81
|
+
/** HMAC secret for the deletion verify token. */
|
|
82
82
|
readonly deletionTokenSecret: string;
|
|
83
|
-
/** Apex
|
|
84
|
-
* URL
|
|
83
|
+
/** Apex route of the confirm screen; the handler appends the token as a
|
|
84
|
+
* URL fragment (`#token=`). */
|
|
85
85
|
readonly deletionVerifyUrl: string;
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
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
|
-
//
|
|
92
|
-
// user-data-rights
|
|
93
|
-
//
|
|
94
|
-
//
|
|
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
|
|
3
|
-
//
|
|
4
|
-
// - entity →
|
|
5
|
-
// - writeHandler →
|
|
6
|
-
// - nav → ID/
|
|
7
|
-
// Plus optionalRequires + metric
|
|
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
|
|
1
|
+
// Encrypted per-tenant config — Stripe API key pattern.
|
|
2
2
|
//
|
|
3
|
-
// Minimal
|
|
4
|
-
// Stripe
|
|
5
|
-
//
|
|
6
|
-
// r.screen/r.nav
|
|
7
|
-
// (
|
|
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:
|
|
10
|
-
// Pattern in
|
|
11
|
-
// charge
|
|
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
|
|
44
|
-
//
|
|
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
|
|
70
|
-
// { headers: { Authorization: `Bearer ${apiKey}` }, ... })
|
|
71
|
-
//
|
|
72
|
-
// response
|
|
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
|
-
//
|
|
87
|
-
// buildConfigFeatureSchema
|
|
88
|
-
// config:write:set
|
|
89
|
-
//
|
|
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
|
|
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
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
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-
|
|
1
|
+
// Legal-pages sample
|
|
2
2
|
//
|
|
3
|
-
// DACH
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
8
|
+
// Solution: combine two opt-in bundled features:
|
|
9
9
|
//
|
|
10
|
-
// - `template-resolver` —
|
|
11
|
-
// kind `text-block`
|
|
12
|
-
//
|
|
13
|
-
// legal-
|
|
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
|
|
16
|
-
//
|
|
17
|
-
// `/legal/privacy`)
|
|
18
|
-
//
|
|
19
|
-
//
|
|
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
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// query
|
|
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
|
-
//
|
|
27
|
-
// - `anonymousAccess`
|
|
28
|
-
// (defaultTenantId = SYSTEM_TENANT_ID),
|
|
29
|
-
// legal-pages
|
|
30
|
-
// - `extraContext.templateResolver = createTemplateResolverApi(db, registry)`
|
|
31
|
-
//
|
|
32
|
-
// -
|
|
33
|
-
// template-resolver/seeding `seedTextBlock`
|
|
34
|
-
// `template-resolver:write:set`
|
|
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
|
|
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.
|
|
15
|
-
//
|
|
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
|
-
//
|
|
3
|
+
// Local smoke test for seed migrations.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
// 1.
|
|
7
|
-
// →
|
|
8
|
-
// 2.
|
|
9
|
-
//
|
|
10
|
-
// (
|
|
11
|
-
// 3.
|
|
12
|
-
//
|
|
13
|
-
// (
|
|
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
|
-
//
|
|
15
|
+
// Writes NOTHING to the DB. Pure read-only validation.
|
|
16
16
|
//
|
|
17
17
|
// Usage:
|
|
18
|
-
// bun scripts/smoke.ts (
|
|
18
|
+
// bun scripts/smoke.ts (against ./seeds/)
|
|
19
19
|
// bun scripts/smoke.ts --seeds-dir foo (custom path)
|
|
20
20
|
//
|
|
21
|
-
// In CI:
|
|
22
|
-
//
|
|
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-
|
|
27
|
-
//
|
|
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
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
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
|
|
1
|
+
// Initial seed: create demo tenants.
|
|
2
2
|
//
|
|
3
|
-
// Note: in real apps
|
|
4
|
-
// seed
|
|
5
|
-
//
|
|
6
|
-
// seed
|
|
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:
|
|
9
|
-
//
|
|
10
|
-
// (find*)
|
|
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
|
-
//
|
|
18
|
-
// seed), skip
|
|
19
|
-
//
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// access_denied
|
|
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
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// updateMemberRoles
|
|
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
|
|
29
|
-
// m.tenantId (payload
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// publicstatus
|
|
33
|
-
//
|
|
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 —
|
|
1
|
+
// User-Data-Rights — recipe
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// (export, delete)
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
|
9
|
-
// title, body).
|
|
10
|
-
// 1.
|
|
11
|
-
// 2.
|
|
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
|
-
//
|
|
14
|
-
//
|
|
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
|
|
17
|
-
// - Compliance
|
|
16
|
+
// - HTTP endpoints for create/list
|
|
17
|
+
// - Compliance profile wiring + cron scheduling
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
deleteMany,
|