feat: taste onboarding for new listeners (M6)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / db / db (push) Failing after 1m29s
ci / ts / ts (packages + web) (push) Successful in 49s
ci / api / api (push) Successful in 1m25s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 48s
ci / recommender / recommender (push) Successful in 21s

- GET /onboarding offers the library's top artists (with album covers)
  and genres and says whether to show it (not done and < 10 plays);
  PUT /me/onboarding stores the choice or a skip (migration 00009).
- The recommender seeds the taste from it while the user has fewer than
  30 scored tracks: chosen artists' tracks +1, chosen genres +0.5.
- Web: onboarding replaces the wave on the home page until done or
  skipped; artist grid with covers, genre chips, at least 3 artists.
- e2e journey and the recommender DB test cover it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 22:35:43 +03:00
1 parent ff0aafdb1e
commit fb07f962eb
14 files changed
+1137 -238

No files matched your search

+23 -2
View File
@@ -1,5 +1,5 @@
import { unwrap } from '@pipu/core';
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -24,6 +24,7 @@ import {
import { cx, FormError, Icon } from '../shared/ui/ui';
import { WaveButton } from '../widgets/wave/WaveButton';
import s from './home.module.css';
import { Onboarding } from './Onboarding';
/** Иконки Material Symbols по именам из /wave/options. */
const ICONS: Record<string, string> = {
@@ -44,8 +45,28 @@ const WAVE_COLORS: [string, string, string] = ['#2F80FF', '#7C5CFF', '#35D3FF'];
type Tr = (key: string) => string;
/** Главная (раскладка Center): кнопка «Моя волна» и под ней выбор занятия и настроения. */
/** Главная: для нового пользователя без истории — сначала онбординг вкуса, затем волна. */
export function HomePage() {
const session = useSession();
const queryClient = useQueryClient();
const onboarding = useQuery({
queryKey: ['onboarding'],
queryFn: async () => unwrap(await session.client.GET('/api/v1/onboarding')),
staleTime: Infinity,
});
if (onboarding.data?.required) {
return (
<Onboarding
options={onboarding.data}
onDone={() => queryClient.setQueryData(['onboarding'], { ...onboarding.data, required: false })}
/>
);
}
return <WaveHome />;
}
/** Главная (раскладка Center): кнопка «Моя волна» и под ней выбор занятия и настроения. */
function WaveHome() {
const { t } = useTranslation();
const tr = t as unknown as Tr;
const session = useSession();