@cosmicdrift/kumiko-samples 0.352.0 → 0.354.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 +18 -2
- package/packages/bundled-features/src/agent-tools/agent-doc-lint.ts +66 -0
- package/packages/bundled-features/src/agent-tools/agent-manifest.ts +26 -7
- package/packages/bundled-features/src/agent-tools/changes.json +12 -0
- package/packages/bundled-features/src/agent-tools/feature.ts +13 -2
- package/packages/bundled-features/src/agent-tools/index.ts +6 -1
- package/packages/bundled-features/src/agent-tools/manifest-deny-qns.ts +25 -0
- package/packages/bundled-features/src/agent-tools/tool-catalog.ts +4 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +1 -1
- package/packages/bundled-features/src/audit/changes.json +6 -0
- package/packages/bundled-features/src/audit/handlers/list.query.ts +20 -10
- package/packages/bundled-features/src/auth-email-password/changes.json +24 -0
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +21 -0
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +4 -1
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +7 -3
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +53 -96
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/index.ts +12 -0
- package/packages/bundled-features/src/auth-email-password/seeding.ts +38 -7
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +83 -0
- package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +9 -3
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +7 -7
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +15 -0
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +2 -2
- package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +1 -1
- package/packages/bundled-features/src/auth-mfa/changes.json +7 -0
- package/packages/bundled-features/src/auth-mfa/feature.ts +5 -0
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -0
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -0
- package/packages/bundled-features/src/auth-mfa/screens.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +6 -8
- package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +18 -21
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +2 -2
- package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +7 -8
- package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +14 -2
- package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +7 -1
- package/packages/bundled-features/src/cap-overview/access.ts +2 -9
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +5 -2
- package/packages/bundled-features/src/cap-overview/handlers/tenant-options.query.ts +6 -9
- package/packages/bundled-features/src/channel-in-app/changes.json +8 -1
- package/packages/bundled-features/src/channel-in-app/in-app-channel.ts +1 -1
- package/packages/bundled-features/src/channel-in-app/tables.ts +18 -12
- package/packages/bundled-features/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +5 -4
- package/packages/bundled-features/src/config/handlers/values.query.ts +7 -2
- package/packages/bundled-features/src/crypto-shredding/changes.json +12 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +154 -69
- package/packages/bundled-features/src/delivery/changes.json +6 -0
- package/packages/bundled-features/src/delivery/db/queries/address-opt-outs.ts +18 -12
- package/packages/bundled-features/src/delivery/delivery-service.ts +103 -16
- package/packages/bundled-features/src/delivery/public-names.ts +1 -1
- package/packages/bundled-features/src/delivery/unsubscribe.ts +81 -13
- package/packages/bundled-features/src/feature-toggles/feature.ts +2 -3
- package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +34 -5
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +31 -8
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +10 -1
- package/packages/bundled-features/src/files/changes.json +24 -0
- package/packages/bundled-features/src/files-tenant-data/changes.json +5 -0
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +4 -0
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +75 -25
- package/packages/bundled-features/src/folders/changes.json +6 -0
- package/packages/bundled-features/src/folders/feature.ts +2 -12
- package/packages/bundled-features/src/folders/handlers/delete-folder.write.ts +41 -9
- package/packages/bundled-features/src/form-draft/changes.json +7 -1
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +4 -2
- package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +18 -6
- package/packages/bundled-features/src/foundation-shared/index.ts +1 -0
- package/packages/bundled-features/src/foundation-shared/mail-host-policy.ts +16 -4
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +6 -0
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +2 -0
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +3 -4
- package/packages/bundled-features/src/jobs/changes.json +30 -0
- package/packages/bundled-features/src/jobs/constants.ts +1 -0
- package/packages/bundled-features/src/jobs/db/queries/retention.ts +12 -0
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +26 -2
- package/packages/bundled-features/src/jobs/feature.ts +7 -1
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +45 -12
- package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +94 -0
- package/packages/bundled-features/src/jobs/i18n.ts +1 -0
- package/packages/bundled-features/src/jobs/index.ts +2 -0
- package/packages/bundled-features/src/jobs/job-run-logger.ts +177 -10
- package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +27 -8
- package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +55 -0
- package/packages/bundled-features/src/ledger/changes.json +23 -0
- package/packages/bundled-features/src/ledger/entity.ts +17 -5
- package/packages/bundled-features/src/ledger/feature.ts +3 -0
- package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +15 -3
- package/packages/bundled-features/src/ledger/handlers/create-transaction.write.ts +1 -0
- package/packages/bundled-features/src/ledger/handlers/reverse-transaction.write.ts +1 -0
- package/packages/bundled-features/src/ledger/schedule-subject-pair.ts +27 -0
- package/packages/bundled-features/src/ledger/schemas.ts +8 -2
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -15
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +24 -11
- package/packages/bundled-features/src/notes-history/changes.json +13 -0
- package/packages/bundled-features/src/notes-history/entity.ts +4 -0
- package/packages/bundled-features/src/notes-history/feature.ts +2 -12
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -0
- package/packages/bundled-features/src/personal-access-tokens/screens.ts +24 -3
- package/packages/bundled-features/src/seo/changes.json +5 -0
- package/packages/bundled-features/src/seo/feature.ts +23 -7
- package/packages/bundled-features/src/sessions/feature.ts +21 -8
- package/packages/bundled-features/src/sessions/handlers/detail.query.ts +34 -33
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +68 -65
- package/packages/bundled-features/src/sessions/screens.ts +10 -8
- package/packages/bundled-features/src/shared/assert-no-where-rule-in-ownership-write.ts +25 -0
- package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +15 -8
- package/packages/bundled-features/src/shared/index.ts +4 -2
- package/packages/bundled-features/src/shared/parent-visibility.ts +31 -1
- package/packages/bundled-features/src/shared/rate-limited-text-response.ts +11 -0
- package/packages/bundled-features/src/shared/row-bound-grant.ts +18 -2
- package/packages/bundled-features/src/shared/session-locale-field.ts +7 -0
- package/packages/bundled-features/src/tags/feature.ts +2 -12
- package/packages/bundled-features/src/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +5 -0
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +2 -2
- package/packages/bundled-features/src/tenant/changes.json +19 -0
- package/packages/bundled-features/src/tenant/decrypt-tenant-names.ts +17 -0
- package/packages/bundled-features/src/tenant/feature.ts +8 -1
- package/packages/bundled-features/src/tenant/handlers/me.query.ts +4 -1
- package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +7 -3
- package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +11 -5
- package/packages/bundled-features/src/tenant/handlers/toggle-enabled.write.ts +2 -0
- package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -0
- package/packages/bundled-features/src/tenant/index.ts +1 -0
- package/packages/bundled-features/src/tenant/invitation-table.ts +6 -0
- package/packages/bundled-features/src/tenant/invited-membership.ts +45 -12
- package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -6
- package/packages/bundled-features/src/tenant/screens.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/changes.json +12 -0
- package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +12 -19
- package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +14 -96
- package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +16 -0
- package/packages/bundled-features/src/tenant-lifecycle/changes.json +22 -0
- package/packages/bundled-features/src/tenant-lifecycle/constants.ts +8 -0
- package/packages/bundled-features/src/tenant-lifecycle/events.ts +6 -0
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +14 -1
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +105 -29
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +64 -10
- package/packages/bundled-features/src/tenant-lifecycle/testing.ts +58 -0
- package/packages/bundled-features/src/tier-engine/changes.json +5 -0
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -0
- package/packages/bundled-features/src/user/changes.json +15 -0
- package/packages/bundled-features/src/user/handlers/detail.query.ts +1 -0
- package/packages/bundled-features/src/user/handlers/list.query.ts +8 -2
- package/packages/bundled-features/src/user/handlers/update.write.ts +1 -0
- package/packages/bundled-features/src/user/seeding.ts +34 -0
- package/packages/bundled-features/src/user-data-rights/boot-checks.ts +7 -0
- package/packages/bundled-features/src/user-data-rights/changes.json +6 -0
- package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -0
- package/packages/bundled-features/src/user-data-rights/index.ts +1 -0
- package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +17 -9
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -2
- package/packages/bundled-features/src/workflow-runner/changes.json +5 -0
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +80 -14
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +11 -1
- package/packages/bundled-features/src/workflow-runner/runner.ts +8 -2
- package/samples/apps/showcase/src/features/items/feature.ts +2 -1
- package/samples/apps/showcase/src/features/items/schema/item.ts +0 -1
- package/samples/apps/styleguide/src/features/widgets/demo-locale.ts +47 -0
- package/samples/apps/styleguide/src/features/widgets/feature.ts +131 -15
- package/samples/apps/styleguide/src/features/widgets/i18n.ts +40 -0
- package/samples/apps/styleguide/src/features/widgets/web/DashboardFilterEcho.tsx +5 -4
- package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +4 -0
- package/samples/apps/ui-walkthrough/package.json +3 -0
- package/samples/apps/use-all-bundled/README.md +1 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
- package/samples/apps/user-data-rights-demo/README.md +1 -3
- package/samples/recipes/legal-pages/README.md +3 -4
- package/samples/recipes/managed-pages/README.md +2 -2
- package/samples/recipes/public-share-token/README.md +4 -5
- package/samples/recipes/public-share-token/src/feature.ts +1 -1
- package/samples/recipes/security-baseline/README.md +6 -0
- package/samples/recipes/seed-migration/README.md +0 -2
- package/samples/recipes/tenant-settings/README.md +1 -1
- package/packages/bundled-features/src/shared/has-where-rule.ts +0 -11
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
WORKFLOW_RUN_STARTED_TYPE,
|
|
49
49
|
WORKFLOW_WAITING_FOR_EVENT_TYPE,
|
|
50
50
|
type WorkflowDefinition,
|
|
51
|
+
WorkflowStepError,
|
|
51
52
|
type WriteEvent,
|
|
52
53
|
type WriteHandlerDef,
|
|
53
54
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
@@ -92,6 +93,11 @@ function checkQ7Fingerprint(
|
|
|
92
93
|
storedFingerprint: string | null,
|
|
93
94
|
): WorkflowRunFailedPayload | null {
|
|
94
95
|
const currentFingerprint = computeDefinitionFingerprint(workflow);
|
|
96
|
+
if (storedFingerprint === null) {
|
|
97
|
+
log.warn(
|
|
98
|
+
`Run ${runId} of workflow "${workflowName}" has no stored definition fingerprint — resuming against the current definition unchecked.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
95
101
|
const fingerprintChanged = storedFingerprint !== null && currentFingerprint !== storedFingerprint;
|
|
96
102
|
if (!fingerprintChanged) {
|
|
97
103
|
return null;
|
|
@@ -387,9 +393,13 @@ async function runResumedPipeline(ctx: HandlerContext, input: ResumedPipelineInp
|
|
|
387
393
|
return { isSuccess: true as const, data: { outcome: "completed" as const } };
|
|
388
394
|
} catch (error) {
|
|
389
395
|
log.warn("workflow run failed", { runId, workflowName, stepIndex, error: String(error) });
|
|
396
|
+
// The input stepIndex is the suspended step; the failure happened at the step the resumed pass reached.
|
|
390
397
|
const failedPayload: WorkflowRunFailedPayload = {
|
|
391
398
|
workflowName,
|
|
392
|
-
stepIndex
|
|
399
|
+
stepIndex:
|
|
400
|
+
error instanceof WorkflowStepError || error instanceof WorkflowSuspensionUnsupportedError
|
|
401
|
+
? error.stepIndex
|
|
402
|
+
: stepIndex,
|
|
393
403
|
error: describeWorkflowStepError(error),
|
|
394
404
|
};
|
|
395
405
|
await appendRunFailed(ctx, runId, failedPayload);
|
|
@@ -75,6 +75,8 @@ export type WorkflowRunFailedPayload = {
|
|
|
75
75
|
readonly reason?: string;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
export type WorkflowRunContext = Pick<HandlerContext, "unsafeAppendEvent">;
|
|
79
|
+
|
|
78
80
|
export class WorkflowSuspensionUnsupportedError extends Error {
|
|
79
81
|
constructor(
|
|
80
82
|
readonly workflowName: string,
|
|
@@ -102,7 +104,7 @@ export async function startAndRunWorkflow(args: {
|
|
|
102
104
|
readonly triggerEvent: WriteEvent;
|
|
103
105
|
readonly triggerEventRef: TriggerEventRef;
|
|
104
106
|
readonly idempotencyKey?: string;
|
|
105
|
-
readonly handlerCtx:
|
|
107
|
+
readonly handlerCtx: WorkflowRunContext;
|
|
106
108
|
}): Promise<{ readonly outcome: "completed" | "suspended" }> {
|
|
107
109
|
const fingerprint = computeDefinitionFingerprint(args.workflow);
|
|
108
110
|
|
|
@@ -123,10 +125,14 @@ export async function startAndRunWorkflow(args: {
|
|
|
123
125
|
|
|
124
126
|
const steps = buildPipelineSteps(args.workflow.pipelineDef, args.triggerEvent);
|
|
125
127
|
|
|
128
|
+
// @cast-boundary msp-to-handler-ctx — event-trigger hands over the narrower
|
|
129
|
+
// MultiStreamApplyContext (no query/write/writeAs). The step vocabulary this
|
|
130
|
+
// runner drives (wait, compute, return) only ever calls unsafeAppendEvent.
|
|
131
|
+
const stepCtx = args.handlerCtx as unknown as HandlerContext;
|
|
126
132
|
const outcome = await runStepList(
|
|
127
133
|
steps,
|
|
128
134
|
args.triggerEvent,
|
|
129
|
-
|
|
135
|
+
stepCtx,
|
|
130
136
|
{},
|
|
131
137
|
{},
|
|
132
138
|
{
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// hängt hier die Handler dran und ruft r.screen/r.nav.
|
|
9
9
|
|
|
10
10
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
11
|
+
import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
|
|
11
12
|
import { itemsTranslations } from "./i18n";
|
|
12
13
|
import {
|
|
13
14
|
itemActiveScreen,
|
|
@@ -29,7 +30,7 @@ const open = {
|
|
|
29
30
|
|
|
30
31
|
// The boot validator checks server-registered keys, while the client bundle is
|
|
31
32
|
// locale-first — flip it rather than maintaining the same strings twice.
|
|
32
|
-
function keyFirst(byLocale:
|
|
33
|
+
function keyFirst(byLocale: TranslationsByLocale): Record<string, Record<string, string>> {
|
|
33
34
|
const out: Record<string, Record<string, string>> = {};
|
|
34
35
|
for (const [locale, entries] of Object.entries(byLocale)) {
|
|
35
36
|
for (const [key, value] of Object.entries(entries)) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Server-side locale helpers for the dashboard demo queries. Not shared with
|
|
2
|
+
// renderer-web's format.ts: server code must not import renderer-web.
|
|
3
|
+
|
|
4
|
+
import { WIDGETS_I18N } from "./i18n";
|
|
5
|
+
|
|
6
|
+
type WidgetsI18nKey = keyof typeof WIDGETS_I18N;
|
|
7
|
+
type DemoCurrency = "EUR" | "USD";
|
|
8
|
+
|
|
9
|
+
const FALLBACK_LOCALE = "en";
|
|
10
|
+
|
|
11
|
+
// Intl throws RangeError on malformed tags.
|
|
12
|
+
function usableLocale(locale: string): string {
|
|
13
|
+
try {
|
|
14
|
+
return Intl.getCanonicalLocales(locale)[0] ?? FALLBACK_LOCALE;
|
|
15
|
+
} catch {
|
|
16
|
+
return FALLBACK_LOCALE;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The bundle only carries de + en: any "de" tag ("de-AT") picks de, everything else en.
|
|
21
|
+
export function demoText(key: WidgetsI18nKey, locale: string): string {
|
|
22
|
+
const entry = WIDGETS_I18N[key];
|
|
23
|
+
const primarySubtag = usableLocale(locale).split("-")[0];
|
|
24
|
+
return primarySubtag === "de" ? entry.de : entry.en;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function demoMoney(amount: number, currency: DemoCurrency, locale: string): string {
|
|
28
|
+
return new Intl.NumberFormat(usableLocale(locale), {
|
|
29
|
+
style: "currency",
|
|
30
|
+
currency,
|
|
31
|
+
maximumFractionDigits: 0,
|
|
32
|
+
}).format(amount);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function demoPercent(fraction: number, locale: string): string {
|
|
36
|
+
return new Intl.NumberFormat(usableLocale(locale), {
|
|
37
|
+
style: "percent",
|
|
38
|
+
maximumFractionDigits: 0,
|
|
39
|
+
}).format(fraction);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function demoMonthYear(year: number, month: number, locale: string): string {
|
|
43
|
+
return Temporal.PlainDate.from({ year, month, day: 1 }).toLocaleString(usableLocale(locale), {
|
|
44
|
+
month: "short",
|
|
45
|
+
year: "numeric",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { demoMoney, demoMonthYear, demoPercent, demoText } from "./demo-locale";
|
|
8
9
|
import { WIDGETS_I18N } from "./i18n";
|
|
9
10
|
|
|
10
11
|
// Statische Demo-Zeitreihe (48 Punkte à 30 Minuten) — kein Date-API,
|
|
@@ -14,6 +15,44 @@ const RESPONSE_POINTS = Array.from({ length: 48 }, (_, i) => ({
|
|
|
14
15
|
value: i === 20 ? null : 120 + Math.round(80 * Math.abs(Math.sin(i / 5))),
|
|
15
16
|
}));
|
|
16
17
|
|
|
18
|
+
const LOAN_START_MS = Date.UTC(2020, 0, 1);
|
|
19
|
+
const LOAN_TODAY_MS = Date.UTC(2026, 5, 15);
|
|
20
|
+
const LOAN_MONTHS = 240;
|
|
21
|
+
const LOAN_PRINCIPAL_MINOR = 25_000_000;
|
|
22
|
+
const LOAN_MONTHLY_RATE = 0.035 / 12;
|
|
23
|
+
|
|
24
|
+
const loanMonthMs = (monthIndex: number): number => Date.UTC(2020, monthIndex, 1);
|
|
25
|
+
|
|
26
|
+
// Annuity loan over 20 years: remaining principal falls, cumulative interest
|
|
27
|
+
// grows. "Today" is a fixed date, not the clock, so the window and the
|
|
28
|
+
// screenshots stay stable.
|
|
29
|
+
const growth = (1 + LOAN_MONTHLY_RATE) ** LOAN_MONTHS;
|
|
30
|
+
const annuityMinor = (LOAN_PRINCIPAL_MINOR * LOAN_MONTHLY_RATE * growth) / (growth - 1);
|
|
31
|
+
let balanceMinor: number = LOAN_PRINCIPAL_MINOR;
|
|
32
|
+
let interestPaidMinor = 0;
|
|
33
|
+
const LOAN_REMAINING_POINTS = [{ atMs: loanMonthMs(0), value: LOAN_PRINCIPAL_MINOR }];
|
|
34
|
+
const LOAN_INTEREST_POINTS = [{ atMs: loanMonthMs(0), value: 0 }];
|
|
35
|
+
for (let i = 1; i <= LOAN_MONTHS; i++) {
|
|
36
|
+
const interest = balanceMinor * LOAN_MONTHLY_RATE;
|
|
37
|
+
balanceMinor = Math.max(0, balanceMinor - (annuityMinor - interest));
|
|
38
|
+
interestPaidMinor += interest;
|
|
39
|
+
LOAN_REMAINING_POINTS.push({ atMs: loanMonthMs(i), value: Math.round(balanceMinor) });
|
|
40
|
+
LOAN_INTEREST_POINTS.push({ atMs: loanMonthMs(i), value: Math.round(interestPaidMinor) });
|
|
41
|
+
}
|
|
42
|
+
const LOAN_SERIES = [
|
|
43
|
+
{ key: "remaining", label: "widgets:dashboard:loan-remaining", points: LOAN_REMAINING_POINTS },
|
|
44
|
+
{ key: "interest", label: "widgets:dashboard:loan-interest", points: LOAN_INTEREST_POINTS },
|
|
45
|
+
];
|
|
46
|
+
const LOAN_MARKERS = [
|
|
47
|
+
{ atMs: loanMonthMs(84), label: { i18nKey: "widgets:dashboard:marker-extra-1" }, kind: "extra" },
|
|
48
|
+
{ atMs: loanMonthMs(150), label: { i18nKey: "widgets:dashboard:marker-extra-2" }, kind: "extra" },
|
|
49
|
+
{
|
|
50
|
+
atMs: loanMonthMs(LOAN_MONTHS),
|
|
51
|
+
label: { i18nKey: "widgets:dashboard:marker-payoff" },
|
|
52
|
+
kind: "payoff",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
17
56
|
const SENDERS = ["William Smith", "Alice Smith", "Bob Johnson", "Emily Davis"] as const;
|
|
18
57
|
const SUBJECTS = ["Meeting Tomorrow", "Re: Project Update", "Weekend Plans", "Re: Budget"] as const;
|
|
19
58
|
|
|
@@ -33,6 +72,13 @@ const INBOX_MESSAGES = Array.from({ length: 18 }, (_, i) => ({
|
|
|
33
72
|
unread: i % 4 === 0,
|
|
34
73
|
}));
|
|
35
74
|
|
|
75
|
+
function openAmount(amount: number, locale: string): string {
|
|
76
|
+
return demoText("widgets:dashboard:demo-amount-open", locale).replace(
|
|
77
|
+
"{amount}",
|
|
78
|
+
demoMoney(amount, "EUR", locale),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
36
82
|
export const widgetsFeature = defineFeature("widgets", (r) => {
|
|
37
83
|
r.screen({ id: "widgets", type: "custom", renderer: { react: { __component: "widgets" } } });
|
|
38
84
|
r.screen({
|
|
@@ -96,6 +142,31 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
|
|
|
96
142
|
chart: "timeseries",
|
|
97
143
|
query: "widgets:query:metrics:response-times",
|
|
98
144
|
},
|
|
145
|
+
{
|
|
146
|
+
kind: "chart",
|
|
147
|
+
id: "loan",
|
|
148
|
+
label: "widgets:dashboard:loan",
|
|
149
|
+
chart: "stacked-area",
|
|
150
|
+
query: "widgets:query:metrics:loan",
|
|
151
|
+
valueFormat: { kind: "currency", currency: "EUR", fractionDigits: 0 },
|
|
152
|
+
brush: true,
|
|
153
|
+
legendTotals: false,
|
|
154
|
+
initialWindow: "from-today",
|
|
155
|
+
markerLegend: "legend",
|
|
156
|
+
markerKinds: {
|
|
157
|
+
extra: { tone: "active", label: "widgets:dashboard:kind-extra" },
|
|
158
|
+
payoff: { tone: "positive", label: "widgets:dashboard:kind-payoff" },
|
|
159
|
+
},
|
|
160
|
+
ranges: {
|
|
161
|
+
default: "max",
|
|
162
|
+
options: [
|
|
163
|
+
{ value: "y1", label: "widgets:dashboard:range-y1", months: 12 },
|
|
164
|
+
{ value: "y3", label: "widgets:dashboard:range-y3", months: 36 },
|
|
165
|
+
{ value: "y5", label: "widgets:dashboard:range-y5", months: 60 },
|
|
166
|
+
{ value: "max", label: "widgets:dashboard:range-all" },
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
99
170
|
{
|
|
100
171
|
kind: "list",
|
|
101
172
|
id: "latest",
|
|
@@ -129,11 +200,14 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
|
|
|
129
200
|
r.queryHandler(
|
|
130
201
|
"metrics:portfolio-stat",
|
|
131
202
|
z.object({ region: z.string().optional() }),
|
|
132
|
-
async ({ payload: { region } }) => ({
|
|
133
|
-
value:
|
|
134
|
-
|
|
203
|
+
async ({ payload: { region } }, ctx) => ({
|
|
204
|
+
value:
|
|
205
|
+
region === "us"
|
|
206
|
+
? demoMoney(38120, "USD", ctx.locale)
|
|
207
|
+
: demoMoney(region === "eu" ? 54630 : 92753, "EUR", ctx.locale),
|
|
208
|
+
sub: demoText("widgets:catalog:portfolio-sub", ctx.locale),
|
|
135
209
|
tone: "positive",
|
|
136
|
-
delta:
|
|
210
|
+
delta: demoPercent(0.12, ctx.locale),
|
|
137
211
|
deltaDirection: "up",
|
|
138
212
|
deltaTone: "positive",
|
|
139
213
|
}),
|
|
@@ -150,7 +224,7 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
|
|
|
150
224
|
r.queryHandler(
|
|
151
225
|
"metrics:net-worth-assets",
|
|
152
226
|
z.object({ region: z.string().optional() }),
|
|
153
|
-
async () => ({ value: "
|
|
227
|
+
async (_query, ctx) => ({ value: demoMoney(120000, "EUR", ctx.locale) }),
|
|
154
228
|
{
|
|
155
229
|
access: {
|
|
156
230
|
openToAll: {
|
|
@@ -164,7 +238,7 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
|
|
|
164
238
|
r.queryHandler(
|
|
165
239
|
"metrics:net-worth-debts",
|
|
166
240
|
z.object({ region: z.string().optional() }),
|
|
167
|
-
async () => ({ value: "
|
|
241
|
+
async (_query, ctx) => ({ value: demoMoney(65370, "EUR", ctx.locale) }),
|
|
168
242
|
{
|
|
169
243
|
access: {
|
|
170
244
|
openToAll: {
|
|
@@ -194,12 +268,40 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
|
|
|
194
268
|
},
|
|
195
269
|
);
|
|
196
270
|
r.queryHandler(
|
|
197
|
-
"metrics:
|
|
271
|
+
"metrics:loan",
|
|
198
272
|
z.object({}),
|
|
199
273
|
async () => ({
|
|
274
|
+
series: LOAN_SERIES,
|
|
275
|
+
markers: LOAN_MARKERS,
|
|
276
|
+
windowStartMs: LOAN_START_MS,
|
|
277
|
+
windowEndMs: loanMonthMs(LOAN_MONTHS),
|
|
278
|
+
todayMs: LOAN_TODAY_MS,
|
|
279
|
+
}),
|
|
280
|
+
{
|
|
281
|
+
access: {
|
|
282
|
+
openToAll: {
|
|
283
|
+
reason:
|
|
284
|
+
"demo dashboard widget: returns static canned data with no per-user or " +
|
|
285
|
+
"tenant scoping; any signed-in user may query it",
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
);
|
|
290
|
+
r.queryHandler(
|
|
291
|
+
"metrics:latest-items",
|
|
292
|
+
z.object({}),
|
|
293
|
+
async (_query, ctx) => ({
|
|
200
294
|
rows: [
|
|
201
|
-
{
|
|
202
|
-
|
|
295
|
+
{
|
|
296
|
+
id: "i1",
|
|
297
|
+
name: demoText("widgets:dashboard:demo-incident-timeout", ctx.locale),
|
|
298
|
+
status: "resolved",
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "i2",
|
|
302
|
+
name: demoText("widgets:dashboard:demo-incident-certificate", ctx.locale),
|
|
303
|
+
status: "done",
|
|
304
|
+
},
|
|
203
305
|
],
|
|
204
306
|
nextCursor: null,
|
|
205
307
|
}),
|
|
@@ -249,10 +351,16 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
|
|
|
249
351
|
r.queryHandler(
|
|
250
352
|
"metrics:upcoming-events",
|
|
251
353
|
z.object({}),
|
|
252
|
-
async () => ({
|
|
354
|
+
async (_query, ctx) => ({
|
|
253
355
|
rows: [
|
|
254
|
-
{
|
|
255
|
-
|
|
356
|
+
{
|
|
357
|
+
primary: demoText("widgets:dashboard:demo-event-rate-adjustment", ctx.locale),
|
|
358
|
+
trailing: demoMonthYear(2026, 8, ctx.locale),
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
primary: demoText("widgets:dashboard:demo-event-savings-contract", ctx.locale),
|
|
362
|
+
trailing: demoMonthYear(2026, 10, ctx.locale),
|
|
363
|
+
},
|
|
256
364
|
],
|
|
257
365
|
}),
|
|
258
366
|
{
|
|
@@ -268,10 +376,18 @@ export const widgetsFeature = defineFeature("widgets", (r) => {
|
|
|
268
376
|
r.queryHandler(
|
|
269
377
|
"metrics:goal-progress",
|
|
270
378
|
z.object({}),
|
|
271
|
-
async () => ({
|
|
379
|
+
async (_query, ctx) => ({
|
|
272
380
|
rows: [
|
|
273
|
-
{
|
|
274
|
-
|
|
381
|
+
{
|
|
382
|
+
label: demoText("widgets:dashboard:demo-goal-mortgage", ctx.locale),
|
|
383
|
+
value: openAmount(42000, ctx.locale),
|
|
384
|
+
fraction: 0.71,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
label: demoText("widgets:dashboard:demo-goal-car-loan", ctx.locale),
|
|
388
|
+
value: openAmount(3200, ctx.locale),
|
|
389
|
+
fraction: 0.92,
|
|
390
|
+
},
|
|
275
391
|
],
|
|
276
392
|
}),
|
|
277
393
|
{
|
|
@@ -19,6 +19,18 @@ export const WIDGETS_I18N = {
|
|
|
19
19
|
"widgets:dashboard:net-worth-assets": { de: "Vermögen", en: "Assets" },
|
|
20
20
|
"widgets:dashboard:net-worth-debts": { de: "Schulden", en: "Debts" },
|
|
21
21
|
"widgets:dashboard:response-times": { de: "Antwortzeit", en: "Response time" },
|
|
22
|
+
"widgets:dashboard:loan": { de: "Kreditverlauf", en: "Loan balance" },
|
|
23
|
+
"widgets:dashboard:loan-remaining": { de: "Restschuld", en: "Remaining balance" },
|
|
24
|
+
"widgets:dashboard:loan-interest": { de: "Gezahlte Zinsen", en: "Interest paid" },
|
|
25
|
+
"widgets:dashboard:kind-extra": { de: "Sondertilgung", en: "Extra repayment" },
|
|
26
|
+
"widgets:dashboard:kind-payoff": { de: "Ablösung", en: "Payoff" },
|
|
27
|
+
"widgets:dashboard:marker-extra-1": { de: "Sondertilgung 2027", en: "Extra repayment 2027" },
|
|
28
|
+
"widgets:dashboard:marker-extra-2": { de: "Sondertilgung 2032", en: "Extra repayment 2032" },
|
|
29
|
+
"widgets:dashboard:marker-payoff": { de: "Kredit getilgt", en: "Loan repaid" },
|
|
30
|
+
"widgets:dashboard:range-y1": { de: "1 Jahr", en: "1 year" },
|
|
31
|
+
"widgets:dashboard:range-y3": { de: "3 Jahre", en: "3 years" },
|
|
32
|
+
"widgets:dashboard:range-y5": { de: "5 Jahre", en: "5 years" },
|
|
33
|
+
"widgets:dashboard:range-all": { de: "Alles", en: "All" },
|
|
22
34
|
"widgets:dashboard:latest": { de: "Neueste Ereignisse", en: "Latest events" },
|
|
23
35
|
"widgets:dashboard:col-name": { de: "Name", en: "Name" },
|
|
24
36
|
"widgets:dashboard:col-status": { de: "Status", en: "Status" },
|
|
@@ -27,6 +39,34 @@ export const WIDGETS_I18N = {
|
|
|
27
39
|
"widgets:dashboard:filter-region": { de: "Region", en: "Region" },
|
|
28
40
|
"widgets:dashboard:filter-region-eu": { de: "Europa", en: "Europe" },
|
|
29
41
|
"widgets:dashboard:filter-region-us": { de: "USA", en: "USA" },
|
|
42
|
+
"widgets:dashboard:filter-echo-title": { de: "Custom-Panel", en: "Custom panel" },
|
|
43
|
+
"widgets:dashboard:filter-echo-filtered": {
|
|
44
|
+
de: "Gefiltert nach Region: {region}",
|
|
45
|
+
en: "Filtered by region: {region}",
|
|
46
|
+
},
|
|
47
|
+
"widgets:dashboard:filter-echo-unfiltered": {
|
|
48
|
+
de: "Ungefiltert (alle Regionen)",
|
|
49
|
+
en: "Unfiltered (all regions)",
|
|
50
|
+
},
|
|
51
|
+
"widgets:dashboard:demo-incident-timeout": {
|
|
52
|
+
de: "API-Timeout eu-central",
|
|
53
|
+
en: "API timeout eu-central",
|
|
54
|
+
},
|
|
55
|
+
"widgets:dashboard:demo-incident-certificate": {
|
|
56
|
+
de: "Zertifikat erneuert",
|
|
57
|
+
en: "Certificate renewed",
|
|
58
|
+
},
|
|
59
|
+
"widgets:dashboard:demo-event-rate-adjustment": {
|
|
60
|
+
de: "Zinsanpassung Baudarlehen",
|
|
61
|
+
en: "Mortgage rate adjustment",
|
|
62
|
+
},
|
|
63
|
+
"widgets:dashboard:demo-event-savings-contract": {
|
|
64
|
+
de: "Bausparvertrag zuteilungsreif",
|
|
65
|
+
en: "Building savings contract ready for allocation",
|
|
66
|
+
},
|
|
67
|
+
"widgets:dashboard:demo-goal-mortgage": { de: "Baudarlehen", en: "Mortgage" },
|
|
68
|
+
"widgets:dashboard:demo-goal-car-loan": { de: "Autokredit", en: "Car loan" },
|
|
69
|
+
"widgets:dashboard:demo-amount-open": { de: "{amount} offen", en: "{amount} outstanding" },
|
|
30
70
|
"widgets:catalog:portfolio": { de: "Portfolio", en: "Portfolio" },
|
|
31
71
|
"widgets:catalog:portfolio-sub": { de: "über 4 Konten", en: "across 4 accounts" },
|
|
32
72
|
"widgets:catalog:remaining-debt": { de: "Restschuld", en: "Remaining debt" },
|
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
// gewählten Screen-Filter-Wert über `filterParams` sieht (siehe
|
|
4
4
|
// DashboardFilterDefinition in feature.ts).
|
|
5
5
|
|
|
6
|
-
import type
|
|
6
|
+
import { type ExtensionSectionProps, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
7
7
|
import { SectionCard } from "@cosmicdrift/kumiko-renderer-web";
|
|
8
8
|
import type { ReactNode } from "react";
|
|
9
9
|
|
|
10
10
|
export function DashboardFilterEcho({ filterParams }: ExtensionSectionProps): ReactNode {
|
|
11
|
+
const t = useTranslation();
|
|
11
12
|
const region = filterParams?.["region"];
|
|
12
13
|
return (
|
|
13
|
-
<SectionCard title="
|
|
14
|
+
<SectionCard title={t("widgets:dashboard:filter-echo-title")}>
|
|
14
15
|
<p className="text-sm text-muted-foreground">
|
|
15
16
|
{typeof region === "string"
|
|
16
|
-
?
|
|
17
|
-
: "
|
|
17
|
+
? t("widgets:dashboard:filter-echo-filtered", { region })
|
|
18
|
+
: t("widgets:dashboard:filter-echo-unfiltered")}
|
|
18
19
|
</p>
|
|
19
20
|
</SectionCard>
|
|
20
21
|
);
|
|
@@ -146,6 +146,10 @@ export function Widgets(): ReactNode {
|
|
|
146
146
|
</div>
|
|
147
147
|
</SectionCard>
|
|
148
148
|
|
|
149
|
+
<div className="flex h-12 w-64 items-stretch" data-testid="progress-stretch-host">
|
|
150
|
+
<ProgressBar value={0.5} testId="progress-stretch-bar" />
|
|
151
|
+
</div>
|
|
152
|
+
|
|
149
153
|
<SectionCard
|
|
150
154
|
title={t("widgets:catalog:repayment-model")}
|
|
151
155
|
action={
|
|
@@ -6,8 +6,7 @@ Canonical smoke-sample. Mounts every bundled-feature so framework-CI catches fea
|
|
|
6
6
|
|
|
7
7
|
Sprint 9.8 found 12 framework bugs in a Studio deployment that all could have
|
|
8
8
|
been caught earlier: 27 of 30 bundled features had zero integration coverage
|
|
9
|
-
from any real app. This sample is the gate
|
|
10
|
-
belong to your private operations documentation.
|
|
9
|
+
from any real app. This sample is the gate.
|
|
11
10
|
|
|
12
11
|
## CI-Gate
|
|
13
12
|
|
|
@@ -1399,7 +1399,7 @@
|
|
|
1399
1399
|
},
|
|
1400
1400
|
{
|
|
1401
1401
|
"name": "jobs",
|
|
1402
|
-
"description": "Persistence and operator tooling for background jobs registered via `r.job(...)`. Every job execution writes directly into `store_job_runs` (current status + duration) and `store_job_run_logs` (per-line log rows) from the BullMQ callbacks — no event stream in between (#2243). A daily `retention-cleanup` job deletes runs (and their logs) older than `retentionDays`; an hourly `stale-run-sweep` job marks runs stuck at status `running` past `staleRunTimeoutHours` as `failed` (#2246 — a crashed worker never fires the completion callback, so nothing else ever revisits the row). Exposes `jobs:write:trigger` (manual run) and `jobs:write:retry` (operator retry of a failed run), plus `jobs:query:list`, `jobs:query:details`, and `jobs:query:catalog` (manual jobs) for the operator UI. A job that declares `tenantVisibleFailure` also records its last failed attempt per tenant and subject in `store_tenant_job_failures`, which the tenant itself reads through `jobs:query:failures` — a translation key only, never the provider's message (fw#3079).",
|
|
1402
|
+
"description": "Persistence and operator tooling for background jobs registered via `r.job(...)`. Every job execution writes directly into `store_job_runs` (current status + duration) and `store_job_run_logs` (per-line log rows) from the BullMQ callbacks — no event stream in between (#2243). A daily `retention-cleanup` job deletes runs (and their logs) older than `retentionDays`; an hourly `stale-run-sweep` job marks runs stuck at status `running` past `staleRunTimeoutHours` as `failed` (#2246 — a crashed worker never fires the completion callback, so nothing else ever revisits the row). Exposes `jobs:write:trigger` (manual run) and `jobs:write:retry` (operator retry of a failed run), plus `jobs:query:list`, `jobs:query:details`, and `jobs:query:catalog` (manual jobs) for the operator UI. A job that declares `tenantVisibleFailure` also records its last failed attempt per tenant and subject in `store_tenant_job_failures`, which the tenant itself reads through `jobs:query:failures` — a translation key only, never the provider's message (fw#3079). A job that declares `tenantVisibleRun` also keeps its run state per tenant and subject in `store_tenant_job_runs` (queued, running, plus the latest completed or failed run, no payload, error text or logs), which the tenant reads through `jobs:query:tenant-runs` (fw#3616); the stale-run sweep fails its stuck `running` rows and drops its stuck `queued` rows.",
|
|
1403
1403
|
"toggleableDefault": null,
|
|
1404
1404
|
"requires": [],
|
|
1405
1405
|
"optionalRequires": [],
|
|
@@ -115,9 +115,7 @@ then 500s). For a real deploy, swap to the persistent provider in
|
|
|
115
115
|
|
|
116
116
|
Then set `S3_BUCKET` / `S3_REGION` / `S3_ACCESS_KEY` / `S3_SECRET_KEY` (plus
|
|
117
117
|
optional `S3_ENDPOINT` for Hetzner/S3-compatible stores) and the app override
|
|
118
|
-
`file-foundation:config:provider = "s3-env"`.
|
|
119
|
-
verification, follow your deployment team's private operations documentation.
|
|
120
|
-
|
|
118
|
+
`file-foundation:config:provider = "s3-env"`.
|
|
121
119
|
## Adding your own domain
|
|
122
120
|
|
|
123
121
|
To add another DSGVO-compliant entity to the demo:
|
|
@@ -87,8 +87,8 @@ imprint" decision.
|
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
# In the app workspace:
|
|
90
|
-
bun kumiko
|
|
91
|
-
bun kumiko
|
|
90
|
+
bun kumiko-schema generate <name> # detects text-block entity → SQL migration
|
|
91
|
+
bun kumiko-schema apply # one-time (pre-deploy step in prod)
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
### 3. Initial seed of the required blocks
|
|
@@ -141,8 +141,7 @@ await seedTextBlock(db, {
|
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
→ Templates for full legally-sound texts: e-recht24.de
|
|
144
|
-
or datenschutz-generator.de by Dr. Schwenke.
|
|
145
|
-
private legal-template documentation for the approved source process.
|
|
144
|
+
or datenschutz-generator.de by Dr. Schwenke.
|
|
146
145
|
|
|
147
146
|
### 4. Visit the pages
|
|
148
147
|
|
|
@@ -81,8 +81,8 @@ the recipe lists `createConfigFeature()` explicitly (see `src/feature.ts`).
|
|
|
81
81
|
### 2. Create the table
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
bun kumiko
|
|
85
|
-
bun kumiko
|
|
84
|
+
bun kumiko-schema generate <name> # detects the `page` entity → SQL migration
|
|
85
|
+
bun kumiko-schema apply
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
### 3. Authoring + branding
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
Minimal pattern for **tokenized anonymous read surfaces**: authenticated users
|
|
4
4
|
mint a link; visitors resolve it without login.
|
|
5
5
|
|
|
6
|
-
Domain-agnostic — no credit/folder logic.
|
|
7
|
-
|
|
8
|
-
(`share-by-token` + 5 layout templates).
|
|
6
|
+
Domain-agnostic — no credit/folder logic. A full consumer adds snapshots,
|
|
7
|
+
layout templates and tier gates on top.
|
|
9
8
|
|
|
10
9
|
## What it shows
|
|
11
10
|
|
|
@@ -16,8 +15,8 @@ Domain-agnostic — no credit/folder logic. Full consumer:
|
|
|
16
15
|
- **`anonymousAccess`** on test stack (see integration test)
|
|
17
16
|
|
|
18
17
|
Client wiring: [`recipes/apex-surface-auth`](../apex-surface-auth/) or a gate
|
|
19
|
-
before `createKumikoApp` (
|
|
20
|
-
|
|
18
|
+
before `createKumikoApp` (a small gate component that renders the public view when
|
|
19
|
+
the URL carries a share token).
|
|
21
20
|
|
|
22
21
|
## Handlers
|
|
23
22
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Pattern: authenticated create → plain token show-once → anonymous
|
|
4
4
|
// read-by-token with SHA-256 lookup. Revoke sets revokedAt via executor.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
6
|
+
// A full consumer adds domain snapshots, layouts and tier gates on top.
|
|
7
7
|
|
|
8
8
|
import { generateToken } from "@cosmicdrift/kumiko-framework/api";
|
|
9
9
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
@@ -54,6 +54,12 @@ export const APP_FEATURES = [
|
|
|
54
54
|
For an app that does not mount `dsgvoSelfServiceFeatures()`, use
|
|
55
55
|
`securityBaselineFeatures()` with its default options, as this recipe does.
|
|
56
56
|
|
|
57
|
+
Dedupe keeps the first mounted instance. Late-bound state such as
|
|
58
|
+
`bindAutoRevokeOnPasswordChange` must be bound on the instance returned by
|
|
59
|
+
`composeFeatures`/`createApp`, not on a locally held handle to the dropped one:
|
|
60
|
+
the dropped instance's hooks never run and password changes silently stop
|
|
61
|
+
revoking sessions.
|
|
62
|
+
|
|
57
63
|
A `createSessionsFeature({ ... })` mounted with different options than the
|
|
58
64
|
preset's own no-arg instance still produces a clear boot error — dedupe only
|
|
59
65
|
collapses provably interchangeable instances, never silently picks one:
|
|
@@ -88,6 +88,4 @@ Wenn eine kaputte Migration den Boot blockiert: setze `skippable: true` im seed-
|
|
|
88
88
|
|
|
89
89
|
## See also
|
|
90
90
|
|
|
91
|
-
- **Plan-Doc:** your deployment team's private operations documentation —
|
|
92
|
-
Phase-2+ Operations (projection-rebuild, event-replay, stream-migration, ...)
|
|
93
91
|
- **Driver-Use-Case:** `seeds/2026-02-01-fix-admin-roles.ts` in this recipe
|
|
@@ -20,7 +20,7 @@ import { defineCreateWithTenantDefaults } from "@cosmicdrift/kumiko-bundled-feat
|
|
|
20
20
|
export const invoiceEntity = createEntity({
|
|
21
21
|
table: "read_invoices",
|
|
22
22
|
fields: {
|
|
23
|
-
title: createTextField({ required: true }),
|
|
23
|
+
title: createTextField({ personal: false, reason: "is_business_data", required: true }),
|
|
24
24
|
amount: createMoneyField({ required: true }),
|
|
25
25
|
language: createSelectField({ options: ["en", "de", "fr"] as const }),
|
|
26
26
|
},
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { OwnershipMap } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
|
|
3
|
-
// `where`-rules are read-path only (buildOwnershipClause). The write path
|
|
4
|
-
// decides in memory against the concrete row, so a where-rule there can only
|
|
5
|
-
// ever deny — boot validation rejects the shape (fw#2626). Feature factories
|
|
6
|
-
// that accept an `ownership` option reject it earlier, at build time, so the
|
|
7
|
-
// author gets the option name in the message instead of an entity scope.
|
|
8
|
-
export function hasWhereRule(map: OwnershipMap | undefined): boolean {
|
|
9
|
-
if (!map) return false;
|
|
10
|
-
return Object.values(map).some((rule) => rule !== "all" && rule.kind === "where");
|
|
11
|
-
}
|