НОВЫЙ UX (от Grok):
- Один экран — табы Вход / Регистрация (мгновенное переключение)
- Все поля видны, кнопка всегда кликабельна
- Валидация в реальном времени: под каждым полем ✓/✗ с пояснением
- Состояния кнопки: idle / load ('⏳ Создаём...') / ok ('✓ Готово!') / err ('⚠️ ...')
- Toast 'Добро пожаловать, [имя]!' после успеха
- При ошибке: подсветка проблемного поля + фокус на нём
- Email живая валидация (regex), пароль ≥6 (как требует Grok / API: ≥8 → я ставлю 8 для совместимости с бэком)
- Пароль 2 — только в режиме Регистрация
- Переключение режима сбрасывает валидацию, фокус на первое пустое поле
CSS новые классы: .btn.ok, .btn.load, .btn.err, input.bad, input.good, .hint.ok/err/mute
Технически:
- showEmailLogin() рисует сразу табы + форму
- _initAuthForm() биндит всё
- _submitAuth(mode) делает реальный запрос
- showEmailRegister() → переключает таб на 'Регистрация'
FIX SESSION:
- Store.save() теперь сохраняет в localStorage ('buhapp_session_v1')
- Store.load() восстанавливает access+refresh
- Store.clear() чистит и память и localStorage
- При загрузке api.js сразу Store.load() — сессия восстанавливается автоматически
- При refresh страницы init() видит Store.access и сразу делает me()
REGISTRATION REDO:
- Отдельный экран showEmailRegister() (не на том же экране что login)
- Поля: имя, email, пароль, пароль2, 18+, соглашения, запомнить
- Полная валидация в реальном времени (имя не пустое, email валидный,
пароль ≥8, пароли совпадают, чекбоксы отмечены)
- Кнопка disabled пока форма не готова, hint показывает что не так
- После успешной регистрации: сразу показ 'Добро пожаловать!' + переход на карту
- Ошибка от сервера — показывается в hint + подсветка поля email красным
- Удалён старый emailRegisterPre и дублирующий emailRegister
- api.js полностью переписан: simple fetch с credentials:'include', без mode:cors
- emailLogin: без 150мс паузы, без retry — same-origin не висит
- nginx: /api/* → backend через тот же домен (убирает cross-subdomain)
- backend cookie: SameSite=Lax, host-only
Анализ Grok: Safari зависает на ответе fetch после POST с Set-Cookie из-за keep-alive/HTTP2 reuse.
- backend Login/Register: Connection: close, Cache-Control: no-store
- backend cookie: SameSite=None (cross-subdomain в Safari)
- frontend api(): cache:'no-store', mode:'cors', timeout 20s
- frontend emailLogin: 150ms пауза после login перед следующим fetch
- showView: requestAnimationFrame + мгновенный loader (UI не висит)
- maybeOnboard на старте fire-and-forget (не блокирует init)
- MapView: НЕ запрашивает геолокацию автоматически (только по кнопке)
- MapView: правильно destroy map если container удалён (нет утечки Leaflet)
- invalidateSize() перед setView чтобы тайлы 2GIS догрузились
- New OnboardingView module (public/js/views/onboarding.js):
• Reads /onboarding/slides.json (designer schema with inline SVG icons,
per-slide cta_primary/cta_skip labels) — falls back to 3 emoji slides
if the JSON is unavailable.
• Renders a Telegram-style dark overlay inside #mobile-frame with a
220px floating SVG icon, title, body, dot indicator (expandable
active dot), primary CTA, and a ghost Skip button.
• Prev/Next via primary CTA; dot click jumps to slide; arrow keys and
horizontal swipe also navigate.
• Haptic feedback via TG_APP on slide change + finish.
• markDone() writes buhapp.onboarded='1' to localStorage so the flow
never repeats. isDone()/reset() exposed for QA.
- App integration (public/js/app.js):
• New App.maybeOnboard() helper — awaited after every successful auth
path: Telegram login, restored session, email login, email register,
and consent accept. Skips silently if already onboarded.
- UI assets (public/onboarding/):
• slides.json with 3 designer slides (Map / Chat / Review).
• Inline-SVG icons for map, chat, and review (icon-*.svg kept for
designer reference, preview.html too).
- Styles (public/css/app.css): new .onboarding-* dark-theme rules.
- Index (public/index.html): loads onboarding.js before app.js.
- vendor/leaflet.js (no more CDN dependency for JS)
- direct tile.openstreetmap.org (was {s}.tile)
- backend GET /api/v1/auth/consents (check if all accepted)
- frontend ConsentApi.status() before showing overlay
- only show overlay once