user-profile
Self-service account page as a bundled feature: change password, change email
(with re-auth + verification reset), and request or cancel account deletion
(with grace period from user-data-rights).
The bundled feature ships the profile screen itself (a declarative
projectionDetail, fw#2312) plus handlers and i18n. Your app only points
a nav entry at it and registers the two extension-section components
(change-password, change-email — re-auth flows a declarative action can’t
express) — this recipe shows the wiring.
What it shows
Section titled “What it shows”user-profile:write:change-email— re-authenticated email change; resetsemailVerifieduntil the user confirms the new address.auth-email-password:write:change-password— used from the same screen; lives onauth-email-password, not duplicated in user-profile.- Account deletion —
user-data-rightshandlers for request / cancel deletion; grace period comes from the tenant’s compliance profile. - App-side nav wiring only —
r.nav({ screen: "user-profile:screen:profile", ... }); nor.screen()registration needed, the bundled feature owns the screen. - Full require chain —
user→tenant→auth-email-password→user-data-rights(which pullsdata-retention,compliance-profiles,sessions).composeAccountApp()mounts everything boot needs.
Feature composition
Section titled “Feature composition”user → cross-tenant identity (email, roles, status)tenant → memberships + tenant-scoped rolesauth-email-password → login + change-password handlersdata-retention → retention policies (via user-data-rights)compliance-profiles → region profiles for grace periodssessions → revocable JWTs (via user-data-rights chain)user-data-rights → deletion request / export pipelineuser-profile → declarative `profile` screen + change-email handleraccount → this recipe: nav wiring onlyClient wiring
Section titled “Client wiring”Register the two extension-section components once at app boot (the screen itself needs no client registration):
import { userProfileClient } from "@cosmicdrift/kumiko-bundled-features/user-profile/web";import { emailPasswordClient } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web";import { createKumikoApp } from "@cosmicdrift/kumiko-renderer-web";
createKumikoApp({ clientFeatures: [emailPasswordClient(), userProfileClient()], // ... schema, nav from server features});- User opens the profile nav entry → renderer loads the declarative
profilescreen. - Password change dispatches
auth-email-password:write:change-password. - Email change dispatches
user-profile:write:change-email(re-auth required). - Delete account dispatches
user-data-rights:write:request-deletion; cancel uses the matching cancel handler during the grace window.
Boot validation only — no DB/HTTP in this recipe (HTTP proof lives in the bundled-feature integration tests):
bun test src/__tests__/feature.test.tsThe test asserts validateBoot(composeAccountApp()) passes, the profile
screen/nav are registered, and the documented change-email qualified name
matches UserProfileHandlers.changeEmail.
Related samples
Section titled “Related samples”- apex-surface-auth — public login/ signup on the marketing apex (no admin nav).
- user-data-rights —
EXT_USER_DATAhooks for export/forget on your domain entities. - apps-user-data-rights-demo — full runnable app with todos + export ZIP.
Source code
Section titled “Source code”The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):
// user-profile Recipe — shows the app-side wiring for the self-service// account page: the bundled feature ships the screen itself (fw#2312:// declarative `projectionDetail`, id "profile") including handler + i18n;// the app only has to navigate to it, no own `r.screen()` registration// anymore (unlike before #2312 — see user-data-rights' recipe/demo pattern:// `r.nav({ screen: "<feature>:screen:<id>", ... })`).//// Client-side, the app only registers the two extension-section components// (change-password/change-email stay React, re-auth):// import { userProfileClient } from// "@cosmicdrift/kumiko-bundled-features/user-profile/web";// createKumikoApp({// clientFeatures: [emailPasswordClient(), userProfileClient()],// })
import { createAuthEmailPasswordFeature } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";import { createComplianceProfilesFeature } from "@cosmicdrift/kumiko-bundled-features/compliance-profiles";import { createConfigFeature } from "@cosmicdrift/kumiko-bundled-features/config";import { createDataRetentionFeature } from "@cosmicdrift/kumiko-bundled-features/data-retention";import { createFilesFeature } from "@cosmicdrift/kumiko-bundled-features/files";import { createPersonalAccessTokensFeature } from "@cosmicdrift/kumiko-bundled-features/personal-access-tokens";import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";import { createTenantFeature } from "@cosmicdrift/kumiko-bundled-features/tenant";import { createUserFeature } from "@cosmicdrift/kumiko-bundled-features/user";import { createUserDataRightsFeature } from "@cosmicdrift/kumiko-bundled-features/user-data-rights";import { createUserDataRightsDefaultsFeature } from "@cosmicdrift/kumiko-bundled-features/user-data-rights-defaults";import { createUserProfileFeature } from "@cosmicdrift/kumiko-bundled-features/user-profile";import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
export function createAccountFeature(): FeatureDefinition { return defineFeature("account", (r) => { r.describe( "App-side wiring for the user-profile bundled feature: just the nav " + "entry — user-profile registers the `profile` screen itself " + "(fw#2312 declarative projectionDetail).", ); r.requires("user-profile");
r.nav({ id: "profile", label: "account:nav.profile", icon: "user", screen: "user-profile:screen:profile", order: 90, });
return {}; });}
/** Volle Feature-Komposition inkl. der user-profile-Require-Kette * (user-data-rights → data-retention + compliance-profiles + sessions). */export function composeAccountApp(): FeatureDefinition[] { return [ createConfigFeature(), createUserFeature(), createTenantFeature(), createAuthEmailPasswordFeature(), createDataRetentionFeature(), createComplianceProfilesFeature(), authFoundationFeature, createPersonalAccessTokensFeature({ scopes: {} }), createSessionsFeature(), createFilesFeature(), createUserDataRightsFeature(), // registers the default export/erase hooks for core PII entities (user, // fileRef, folder) so the GDPR boot gate (V3) is satisfied for the stack. createUserDataRightsDefaultsFeature(), createUserProfileFeature(), createAccountFeature(), ];}📄 On GitHub: samples/recipes/user-profile/src/feature.ts