managarten/packages/shared-branding/src/onboarding-templates.ts
Till JS 1198d01263 feat(onboarding): M4 — Screen 3 (Templates) + finish handler
- packages/shared-branding/onboarding-templates.ts:
  * 7 templates: Alltag / Arbeit / Health / Sport / Lernen / Entdecken
    / Erinnern — each with a phosphor icon name, German name/desc and
    an ordered moduleIds list
  * resolveModulesForTemplates() — deduplicates the union of selected
    templates' modules (priority-ordered) and caps at 8 (2×4 grid)
- packages/shared-branding/onboarding-templates.spec.ts: 10 tests
  covering order preservation, dedup-across-templates, cap honouring,
  unknown-id tolerance
- /onboarding/templates/+page.svelte:
  * Multi-select grid of 7 tiles (checkmark + primary border when on)
  * Finish handler: runs resolveModulesForTemplates → creates a new
    "Zuhause" scene with those apps → onboardingStatus.markComplete()
    → navigates to /
  * Skip still marks complete (no scene — user lands on DEFAULT_HOME_APPS)
  * Prefills selection from onboardingFlow store so back-nav is stable

With this, the 3-screen flow runs end-to-end for a new user:
signup → /onboarding/name → /look → /templates → / with a curated
home scene.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:03:00 +02:00

120 lines
3.5 KiB
TypeScript

/**
* Onboarding templates — starter-packs a user picks on Screen 3 of the
* first-login flow (/onboarding/templates). Each template is a named
* use-case with an ordered list of module IDs. The flow's finish
* handler deduplicates the union of picked templates' modules (keeping
* the first occurrence in priority order), caps the total at 8, and
* writes the result as the user's Home scene.
*
* All module IDs are verified against
* apps/mana/apps/web/src/lib/app-registry/apps.ts — if a template
* references a module that was removed, catch it in the test below
* before the user sees an empty tile.
*
* See docs/plans/onboarding-flow.md for rationale (why these templates,
* why multi-select, why a cap).
*/
export type OnboardingTemplateId =
| 'alltag'
| 'arbeit'
| 'health'
| 'sport'
| 'lernen'
| 'entdecken'
| 'erinnern';
export type OnboardingTemplate = {
id: OnboardingTemplateId;
/** German name rendered on the tile. */
name: string;
/** One-line description under the tile name. */
shortDescription: string;
/** Phosphor icon name — resolved by the consuming page. */
iconName: 'House' | 'Briefcase' | 'Heart' | 'Barbell' | 'GraduationCap' | 'Compass' | 'Camera';
/**
* Module IDs in priority order (first = most important). Dedup
* across templates keeps the earliest occurrence; cap = 8.
*/
moduleIds: string[];
};
export const ONBOARDING_TEMPLATES: readonly OnboardingTemplate[] = [
{
id: 'alltag',
name: 'Alltag',
shortDescription: 'Aufgaben, Termine, Notizen, Kontakte',
iconName: 'House',
moduleIds: ['todo', 'calendar', 'notes', 'contacts'],
},
{
id: 'arbeit',
name: 'Arbeit',
shortDescription: 'Produktivität für den Job',
iconName: 'Briefcase',
moduleIds: ['todo', 'calendar', 'mail', 'chat', 'times', 'notes'],
},
{
id: 'health',
name: 'Health',
shortDescription: 'Gesundheit, Stimmung, Ernährung, Zyklus',
iconName: 'Heart',
moduleIds: ['habits', 'body', 'mood', 'food', 'period'],
},
{
id: 'sport',
name: 'Sport',
shortDescription: 'Training, Ziele, Körper, Ernährung',
iconName: 'Barbell',
moduleIds: ['habits', 'body', 'food', 'goals', 'stretch'],
},
{
id: 'lernen',
name: 'Lernen',
shortDescription: 'Skills, Quizzes, Notizen, Kontext',
iconName: 'GraduationCap',
moduleIds: ['skilltree', 'quiz', 'notes', 'library', 'kontext'],
},
{
id: 'entdecken',
name: 'Entdecken',
shortDescription: 'Orte, Fotos, Musik, Wetter',
iconName: 'Compass',
moduleIds: ['places', 'citycorners', 'photos', 'music', 'wetter'],
},
{
id: 'erinnern',
name: 'Erinnern',
shortDescription: 'Memoro, Journal, Fotos, Zitate',
iconName: 'Camera',
moduleIds: ['memoro', 'journal', 'photos', 'moodlit', 'quotes'],
},
];
/**
* Pick modules for the user's Home scene based on a multi-selection of
* templates. Iterates templates in the order provided, collecting their
* modules while skipping dups and stopping at the cap.
*
* Exported so the onboarding page and its test can share the same
* logic — the page only has to worry about UI state.
*/
export function resolveModulesForTemplates(
selectedIds: readonly OnboardingTemplateId[],
cap = 8
): string[] {
const byId = new Map(ONBOARDING_TEMPLATES.map((t) => [t.id, t]));
const seen = new Set<string>();
const out: string[] = [];
for (const id of selectedIds) {
const tpl = byId.get(id);
if (!tpl) continue;
for (const moduleId of tpl.moduleIds) {
if (seen.has(moduleId)) continue;
seen.add(moduleId);
out.push(moduleId);
if (out.length >= cap) return out;
}
}
return out;
}