ЧИТИРИ#3
Merged
Merged
Conversation
- Switch to branch feat/mvp-rewrite (D-16) - Remove web-map/src (Leaflet code) — old code remains on main - .env confirmed gitignored, untracked, no leak in git history - VITE_YMAP_KEY already present (D-04 corrected: no rename needed)
- Remove leaflet, react-leaflet, @types/leaflet
- Add MVP runtime stack: @tanstack/react-query, zustand, nuqs, msw, vaul,
use-debounce, date-fns, react-router, react-hook-form, zod,
@hookform/resolvers, lucide-react, clsx, tailwind-merge, react-error-boundary,
@yandex/ymaps3-default-ui-theme
- Add dev tooling: @yandex/ymaps3-types, vitest, @vitest/ui, happy-dom,
@testing-library/{react,jest-dom,user-event}, @playwright/test, prettier,
prettier-plugin-tailwindcss, eslint-config-prettier
- Initialize husky + lint-staged
- Used --legacy-peer-deps because react-query-devtools 5.100.2 lists peer
react-query@^5.100.2 but only 5.100.1 is published
- 4 tests for env config: valid parse, empty key rejection, default for VITE_AUTH_MODE, default for VITE_API_BASE_URL - Forward-declare tests/setup.ts (will be expanded in Plan 02) - Wire @ alias and vitest config in vite.config.ts - Add baseUrl + paths + types to tsconfig.app.json
- src/{app,pages/map,widgets,features,entities,shared} layers
- shared/config: zod-typed env (VITE_YMAP_KEY required, AUTH_MODE/API_BASE_URL with defaults)
- shared/config/constants.ts: ITMO_CENTER (lon,lat), DEFAULT_ZOOM,
VIEWPORT_DEBOUNCE_MS, BBOX_ROUND_DECIMALS
- shared/ui/Spinner.tsx: a11y atom (role=status, sr-only label)
- .env.example: documented env template
- src/main.tsx: placeholder bootstrap rendering Spinner
(Plan 02 will replace with router + providers)
- eslint.config.js: no-restricted-imports patterns ban features↔features and entities↔entities imports (FSD Rules 3-4) - eslint-config-prettier appended last to disable formatting rules - .prettierrc: 100 cols, single quotes, trailing comma all, prettier-plugin-tailwindcss for class sorting - .prettierignore: dist, node_modules, coverage, MSW worker, lockfile - package.json: scripts (format, format:check, test, test:watch, test:e2e), lint-staged config - .husky/pre-commit: runs lint-staged - vite.config.ts: drop unused proxy callback args (lint fix) - Pre-existing files (yml/html/md/json) reformatted by Prettier - FSD self-test: temp @/features/other/model/x import → ESLint error reported correctly; deleted → lint clean
- axios instance с adapter:'fetch' для MSW 2 + 401-перехватчик (parktrack:unauthorized) - AuthAdapter контракт: AuthStatus + User + интерфейс - mock-adapter через TanStack Query на /auth/me (staleTime/gcTime: Infinity) - shared-adapter — Phase-5 stub - useAuth — переключение по env.VITE_AUTH_MODE - AuthReady — Suspense-like гейт против race-condition (Pitfall #7, FOUND-09) - entities/user — useUserProfile() обёртка над GET /users/me
…trap - QueryProvider: staleTime 30s, retry 1, refetchOnWindowFocus=false; Devtools только в DEV - queryClient вынесен в отдельный модуль (react-refresh/only-export-components) - RootErrorBoundary через react-error-boundary с локализованным fallback - AppProviders: RootErrorBoundary > NuqsAdapter > QueryProvider > AuthReady > children - main.tsx: enableMocking() async-bootstrap MSW в DEV или AUTH_MODE='mock' - BrowserRouter + Routes (/ и /map) с MapPage placeholder - src/index.css: tailwindcss v4 entry
…раторы - public/mockServiceWorker.js (msw init) - mocks/browser.ts + mocks/node.ts (setupWorker + setupServer) - generators/users.ts: AuthMe + UserProfile (mock-форма из docs api/) - generators/zones.ts: 200 зон вокруг ИТМО, Mulberry32 PRNG seed=42 - generators/occupancy.ts: timeseries с baseline по часу/дню недели - generators/forecasts.ts: будущие точки с расширяющимся std и затухающим confidence - handlers.ts: /auth/me (delay 500ms в DEV), /users/me, /zones, /zones/:id, /occupancy, /forecasts, /routing/search, /routing/new (8 шт.)
- tests/setup.ts: jest-dom matchers + MSW node server (server.listen 'error') + ymaps3 vi.mock (Pitfall #19 forward-declared для Plan 03) - tests/unit/env.spec.ts: 4 теста для EnvSchema (FOUND-10 acceptance) - playwright.config.ts: chromium-проект, webServer 'npm run dev', port 5173 - tests/e2e/smoke.spec.ts: smoke-скелет (полноценный E2E в Plan 03) - vite.config: exclude tests/e2e из vitest, чтобы Playwright и Vitest не конфликтовали
…3 touch-point) - index.html: <script src=api-maps.yandex.ru/v3?apikey=%VITE_YMAP_KEY%&lang=ru_RU> - src/shared/lib/ymaps/index.ts: единственный модуль, обращающийся к window.ymaps3 (Anti-Pattern #5) reactify.bindTo(React, ReactDOM); экспортирует YMap*, YMapZoom/GeolocationControl, useDefault - src/shared/lib/ymaps/types.ts: re-export LngLat/DrawingStyle/YMapLocationRequest для потребителей - Pitfall #1 + MAP-07 задокументированы в шапке index.ts
- bbox.spec.ts: roundBbox5 (3 теста) — округление до 5 знаков, стабильность, bboxFromBounds - zone-style.spec.ts: computeZoneStyle (2 теста) — мемоизация по 5-частному ключу Тесты сейчас падают (модули создаются на GREEN-этапе).
…le stub - shared/lib/geo: Bbox/MapBounds типы, roundBbox5 (MAP-06), bboxFromBounds, bbox<->string - shared/lib/url/parsers: parseAsBbox — кастомный nuqs createParser - entities/zone: ZoneMapItem (PolygonGeometry inline) + TimeMode + fetchZones (signal) + useZonesQuery queryKey ['zones', mode, round5(bbox)] + keepPreviousData + staleTime 30s (MAP-05/06/08) - widgets/map-canvas/model/zone-style.ts: computeZoneStyle с 5-частным cache-key (Phase 1 stub colors) Deviation [Rule 3 — Blocking]: @types/geojson не установлен в Plan 01/02 — определил PolygonGeometry inline в zone.types.ts вместо добавления нового пакета. Все 5 unit-тестов проходят (3 bbox + 2 zone-style).
…tZones (read) - widgets/map-canvas/model/useBboxTracking: useDebouncedCallback(400мс) → round5 → setBbox skip-write если round5(next) === bbox (jitter-free, MAP-04/06, Pitfall #2) - features/viewport-driven-zones/model/useViewportZones: чтение bbox из URL → useZonesQuery feature НЕ импортирует из @/widgets (FSD: useQueryState дублируется) - mode='now' захардкожен; Phase 3 заменит на timeMode-адаптер
…ndary - widgets/map-canvas/ui/MapCanvas: единственный владелец YMap-ref (MAP-01/02) YMapDefaultSchemeLayer + YMapDefaultFeaturesLayer (MAP-03 — встроенный парковочный слой) YMapListener.onUpdate → useBboxTracking.writeBbox; YMapZoomControl справа - widgets/map-canvas/ui/MapSkeleton: animate-pulse контейнер, role=status (UX-01) - widgets/map-canvas/ui/ZoneLayer: debug-overlay с количеством зон (Phase 2 заменит на полигоны) + MAP-09 inline note: clusterer threshold ~500 — измерить spike'ом в Phase 2 - app/errors/MapErrorBoundary: react-error-boundary вокруг MapCanvas; fallback с кнопкой «Перезагрузить карту» при CDN-fail (MAP-07) - pages/map/MapPage: React.lazy(MapCanvas) + Suspense(MapSkeleton) + MapErrorBoundary window.ymaps3 — единственный файл src/shared/lib/ymaps/index.ts (Anti-Pattern #5 enforced). 13/13 unit-тестов зелёные, lint/tsc чистые.
- map.spec.ts:
* 'карта монтируется ...' — ждёт зональный overlay через getByTestId('zone-count')
* 'MAP-05: непрерывный пан 5с → не более 3 запросов /zones' — считает реальные
/zones-запросы во время drag-loop
- npx playwright test --list ↦ 3 теста (smoke + 2 map.spec)
Manual-verify steps задокументированы в SUMMARY (CDN block, deeplink, AuthReady race).
…я Playwright - .gitignore: разделил .env / playwright-report (regexp слил их в одну строку); добавил test-results - .prettierignore: playwright-report + test-results - prettier --write нормализовал index.html (CDN-script на одну строку), src/index.css, tests/setup.ts (минорные пробелы/EOL) Final gates на feat/mvp-rewrite (Node v24.11.0): - npm run lint exits 0 - npm run format:check exits 0 - npx tsc -b --noEmit exits 0 - npm run test 4 файла / 13 тестов — green - npm run build успешен (385 kB JS gzip 123 kB, 9.36 kB CSS) - npx playwright test --list 3 теста дискаверятся Phase 1 код полностью на feat/mvp-rewrite.
…t fix + playwright 127.0.0.1 - Dockerfile: node:18-alpine -> node:20-alpine (R-1 from 01-01-SUMMARY) - npm ci с --legacy-peer-deps (R-2 from 01-01-SUMMARY) - MapPage: lazy import через subpath (web-map/ui/...), чтобы Vite не втянул @/shared/lib/ymaps в главный chunk → top-level await не падает ДО монтажа React → MapErrorBoundary реально работает - Playwright: 127.0.0.1 вместо localhost (Windows IPv6 quirk) - package-lock.json: регенерирован после --legacy-peer-deps install
Failing tests за parallel-geometry.spec.ts (3 кейса) и centroid.spec.ts (2 кейса). Модули @/shared/lib/geo/parallel и @/shared/lib/geo/centroid пока не существуют — добавятся в GREEN-коммите Task 1.
- shared/config/zone-palette.ts: 5-цветная D-01 палитра + CONFIDENCE_THRESHOLD - shared/config/constants.ts: +ZONE_BADGE_MIN_ZOOM=14 (D-02), +FILTER_STORAGE_PREFIX (D-11) - shared/lib/geo/parallel.ts: polygonToParallelLine — D-04 геометрия - shared/lib/geo/centroid.ts: zoneCentroid для бейджей и центрирования - entities/zone/model/zone.types.ts: ZoneMapItem приведён к форме MSW (zone_id: number вместо id: string; +location_type, +is_private, +is_accessible, +occupancy_updated_at, +confidence_level, +occupied) + добавлен Zone (full model для CARD-01) - barrels: ре-экспорт parallel + centroid + zone-palette + расширенные типы Тесты parallel-geometry.spec.ts (3) и centroid.spec.ts (2) — все зелёные.
Полный rewrite zone-style.spec.ts: 8 кейсов покрывают 5 цветовых правил D-01, selected → strokeWidth 3 (D-08), memoization, selected/unselected как разные cache entries. Старые Phase 1 кейсы (нейтрально-серый STUB) удалены — Phase 2 заменяет поведение целиком. 7/8 тестов падают на старом stub'е (1 проходит — memoization сохраняется), плюс tsc -b ругается на zoneId: number vs string. GREEN-коммит Task 2 закроет.
- StyleKey: zoneId: string -> number; +selected: boolean (D-08) - pickPalette: 5 правил D-01 (inactive | full | one | freeLow | freeHigh) - computeZoneStyle: strokeWidth 3 при selected, 1 иначе - Импорт через named tokens из @/shared/config/zone-palette → Phase 5 swap на UI-kit Миши без рефакторинга consumers Все 8 тестов zone-style.spec.ts зелёные.
- ZoneLayer: REWRITE Phase 1 debug-overlay → реальный рендер standard-зон через
YMapFeatureDataSource + N <YMapFeature> children (Pattern 1 reactify diff)
- ParallelZoneLayer: NEW — D-04 LineString между midpoint'ами коротких сторон,
отдельный datasource zIndex 1901
- ZoneBadgesLayer: NEW — ZONE-06 redundant encoding, free_count pill через
YMapMarker + zoneCentroid; скрыт при zoom < ZONE_BADGE_MIN_ZOOM (=14)
- MapCanvas: zoom трекается локально (setState), 3 новых zone-layer'а композированы
- zone-style.ts: +toDrawingStyle() конвертер ZoneStyle → ymaps3 DrawingStyle
(граничный, чтобы внутренний формат остался plain { fill, stroke, strokeWidth }
для тестов и Phase 5 UI-kit swap)
- map-canvas/index.ts: re-export ZoneLayer/ParallelZoneLayer/ZoneBadgesLayer
- e2e/map.spec.ts: data-testid 'zone-count' (Phase 1 debug-overlay) → 'zone-badge'
(новый сигнал «зоны загрузились»)
selectedZoneId захардкожен false — Plan 02 заменит на useSelectedZone() hook
(?sel= via nuqs). onClick — stub с console.debug; setSelectedZone wiring в Plan 02.
Все 24 unit-теста зелёные; tsc и lint clean; FSD-граница не нарушена.
…ement deferred to HUMAN-UAT) Auto-mode не позволяет реально замерить fps — для этого нужен живой браузер + DevTools Performance panel. Зафиксирован inline-комментарий в ZoneLayer.tsx с: - educated guess: ~50 fps при 200 zones + badges (на основе PITFALLS #2 + reactify-diff pattern) - threshold MVP: 45 fps - ссылка на .planning/.../02-HUMAN-UAT.md item «MAP-09 fps» — пользователь должен выполнить тест и обновить число с реальным measured Дев-сервер успешно стартует (Vite ready ~640мс с 200 фейковыми зонами), tsc/lint/тесты зелёные. До получения реального замера MAP-09 формально считается «закрыт по educated guess», follow-up в Phase 2.x если measured < 45.
- 8 plural кейсов: 0/1/2/5/11/21/22/1.5 — включая критический n=11→many - 3 relative-time кейса: past/future/часы с vi.useFakeTimers - pluralizeRu/formatRelativeRu — пока stub'ы (return '')
- pluralizeRu через Intl.PluralRules('ru') с lazy init.
CLDR 'other' (нецелые числа) маппится на 'few' — речевая норма:
«1,5 места», «2,7 литра» (родительный падеж единственного).
- formatRelativeRu через date-fns formatDistanceToNow + ru locale
(date-fns ^4.1.0 каноничный путь импорта).
- Все 11 тестов зелёные (8 plural включая n=1.5 + 3 relative-time).
- useSelectedZone (features/select-zone) — single source of truth для ?sel=: setSelectedZone (pushState — Back закрывает карточку, URL-07); closeCard (replaceState — без spam history entries, D-14). - fetchZoneById(id, signal) + useZoneByIdQuery в entities/zone — для CARD-01. - ZoneLayer + ParallelZoneLayer wired: onClick → setSelectedZone(z.zone_id) (вместо console.debug stub Plan 01), selected: z.zone_id === selectedZoneId → strokeWidth=3 (D-08 highlight). - Parallel-вариант: stroke-width 6 → 8 при selected. - FSD граница соблюдена: features/select-zone не импортит widgets.
- 8 RTL кейсов на ZoneCardContent (CARD-01..07): loading/Spinner, '5 мест', '1 место', Бесплатно, 200₽/час, Частная (CARD-03), aria-label='Закрыть карточку' (A11Y-02), кнопка 'Построить маршрут' (CARD-05). - ZoneCard/MobileZoneCard — пока stub'ы (return null). - chore: добавлен dev-dep @testing-library/dom (peer для @testing-library/react v16).
…RD-07 mobile pan (GREEN)
- ZoneCard (D-05): desktop right-side panel 400px overlay; aside hidden lg:block;
карта НЕ ужимается (D-05). D-08a: key={selectedZoneId} → smooth re-render.
- ZoneCardContent: header + Spinner/error/data branches; через useZoneByIdQuery.
- ZoneCardBody: free_count + ru-плюрализация (CARD-06), capacity, confidence%,
formatRelativeRu (CARD-02), Бесплатно/₽/час (CARD-04), маркеры zone_type/
location_type/Частная/Для инвалидов (CARD-03/ZONE-04), кнопка маршрута (CARD-05).
- MobileZoneCard (D-06): vaul snap [0.4, 0.85], lg:hidden Portal/Overlay/Content.
CARD-07 mobile pan: useEffect → mapRefHolder.current.setLocation({center, duration:300}).
- MapRefContext вынесен в widgets/map-canvas/model/map-ref-context.ts
(react-refresh/only-export-components rule). Re-exported из barrel.
- MapCanvas: useRef<YMapInstance> + ref={mapRef} к <YMap>; Provider wraps children.
- 8 RTL-тестов GREEN (Spinner loading, '5 мест', '1 место', Бесплатно, 200₽/час,
Частная, aria-label='Закрыть карточку', кнопка 'Построить маршрут').
- MapPage композирует MapCanvas + ZoneCard + MobileZoneCard. ZoneCard: hidden lg:block внутри (desktop overlay). MobileZoneCard: lg:hidden внутри Portal/Content классов (mobile vaul). Оба слушают один useSelectedZone() — синхронизированы через URL ?sel=. - Контейнер: relative + h-screen + w-screen + overflow-hidden — для position:absolute right:0 ZoneCard'а. - Контракт Plan 03 (wave 3): когда введёт DesktopLayout/MobileLayout split, ОБЯЗАН сохранить ZoneCard в DesktopLayout и MobileZoneCard в MobileLayout. - chore: prettier отформатировал zone-card.spec.tsx (polygon coordinates).
- useRoutingSearchBody composes URL ?from/?dest + filters + timeMode → RoutingSearchBody|null - D-14 hardcoded limit:20, provider:'yandex', max_distance_to_destination_meters:500 - D-15 mode dispatch: from && !dest → find_parking; from && dest → route_to_destination - D-41 use_forecast = (timeMode.kind !== 'now') - useAutoSelectBestVariant: write ?sel ONLY when ?sel === null (research Q3 / sticky URL) - 7 vitest specs all green
…tate + scroll sync - ResultItem layout per D-20 verbatim: badge «Лучший вариант» (rank=1), zone_id, free_count/capacity, pay (or «Бесплатно»), forecast row, distance, ETA, confidence - ResultsList — @tanstack/react-virtual useVirtualizer (estimateSize=140, overscan=4) - EmptyResultsState (D-44) с verbatim текстом + reset/close buttons - useResultsScrollSync (D-22) — virtualizer.scrollToIndex когда ?sel ∈ candidates - W-4: zoneCentroid impl. accepts minimal-shape geometry без cast hack - 13 vitest specs all green
…rrel - DesktopResultsPanel: 400px left-side overlay (D-18); CO-03 open=!!from only - MobileResultsSheet: vaul Drawer single-snap [0.92] (CO-02 / B-3 fix) - Mutual-exclusion с MobileZoneCard через open=!!from && selectedZoneId===null (sequential focus, no nested vaul / focus-trap conflict — Pitfall 11) - Auto-select best variant (D-21 / WTP-06) подключён в обоих widget'ах - Empty/Error/Spinner states (D-44, D-45, UX-02) - tsc --noEmit clean
…earch'] (D-42) - Aggregate fetchingCount = fetchingZones + fetchingRouting - Atomic-mode-switch coverage для ResultsPanel (overlay висит пока routing-search не settled) - Context-aware text: «Поиск парковок…» когда routing fetching > 0; «Загрузка данных за выбранное время…» когда zones fetching; fallback «Загрузка…» - Existing 4 tests passing (mocked useIsFetching → returns same value for both subscriptions) - tsc --noEmit clean
…Mobile Layouts - DesktopLayout: <DesktopResultsPanel/> рядом с <ZoneCard/> (z-20 left overlay, z-30 right card). Map центрируется между ними. - MobileLayout: <MobileResultsSheet/> перед <MobileZoneCard/> (mutual-exclusion via selectedZoneId logic; CO-02 single-snap [0.92] sequential focus). - Полный suite 272/272 tests passing; tsc --noEmit + ESLint clean
…sk 1)
- D-28: useRouteId hook (?route=<int> URL state, history='replace')
- CO-05/W-2: useRouteSelSync reverse-sync (?route → ?sel) для reload-recovery
- ROUTE-03/D-29: RoutePreviewLayer — LineString polyline + origin/dest markers
- W-4 fix: zoneCentroid принимает minimal-shape без cast
- key={routeId} для clean reconciliation; viewport не сбрасывается
- Wire RoutePreviewLayer как последний child YMap в MapCanvas
4 vitest tests green; tsc --noEmit clean.
…leDeeplinkSheet (Task 2) - D-32: 3 опции menu (Яндекс Навигатор autoFocus / Яндекс Карты web / Google Maps) - D-33 timer-fallback: visibilitychange listener + setTimeout DEEPLINK_FALLBACK_MS=2500ms → если page всё ещё visible после 2500ms, открываем yandex.ru/maps в новом окне - D-34 coordinate validation: isValidCoords ПЕРЕД сборкой URL; invalid → ptk:deeplink-invalid event - DesktopDeeplinkPopover: radix Popover, [В путь →] trigger disabled при !coordsValid - MobileDeeplinkSheet: vaul Drawer, 3 опции + [Отмена]; min-h-[44px] tap targets 5 vitest tests green; tsc --noEmit clean.
- D-31 RouteSummaryCard: «Маршрут построен» heading + ETA + Intl distance + arrival
- Intl.NumberFormat ru-RU unit:meter для distance
- Intl.DateTimeFormat timeZone:'Europe/Moscow' для arrival_time → «Прибытие в HH:MM МСК»
- Embeds DesktopDeeplinkPopover (lg:block) и MobileDeeplinkSheet (lg:hidden)
- coordsValid := isValidCoords(from) && isValidCoords([zoneLat, zoneLon])
- D-30 FitToRouteButton: user-initiated bbox fit, bottom-right (z-25)
- aria-label «Показать весь маршрут»
- bbox охватывает origin + zone_centroid → map.setLocation({bounds, duration:400})
- W-4 zoneCentroid принимает minimal-shape (no cast hack)
- Barrel exports complete: useRouteId/useRouteSelSync/RouteSummaryCard/FitToRouteButton
4 RouteSummaryCard tests green; tsc --noEmit clean.
…s ?route+?sel (Task 4) - D-27 / ROUTE-01: BuildRouteSection в ZoneCardBody - useRoutingSearchBody (?from + ?dest + filters + timeMode) + selected_zone_id - useCreateRouteMutation.mutateAsync → setRouteId(route.route_id) - On success → routeId !== null → render RouteSummaryCard inline (replaces button) - canBuildRoute := body !== null; без ?from prompt-text «укажите стартовую точку» - D-46 errorMsg «Не удалось построить маршрут» + [Повторить] - D-28 close handler: ZoneCard + MobileZoneCard оба зовут handleClose = clearRouteId + closeCard → atomic clear ?route + ?sel при X / outside / Esc click 285/285 vitest tests passing (272 baseline + 13 Phase 4 new); tsc --noEmit clean.
…e (Task 5) - DesktopLayout / MobileLayout: добавлен <FitToRouteButton/> child (gates сам себя по ?route) - tests/e2e/phase4-smoke.spec.ts: - Test 1: search → results → build route → deeplink menu (full sales-funnel) - Test 2: reload с invalid ?route не crashит - Test 3: ?dest reload renders ok - test.skip с reason на ymaps3 CDN failure (Phase 3 blocker per STATE.md) - ROUTE-08 явно помечен deferred-to-Phase-5 в spec header 285/285 vitest tests; tsc + ESLint clean. Playwright execution может skip из-за ymaps3 CDN headless issue — spec остаётся как code asset для Phase 5 polish.
Member
|
These changes are now in development branch, which will be deployed separately. This PR will be merged after it is tested in staging |
- Create shared/lib/dom/useVisualViewportHeight hook (Pitfall 1 fix: iOS Safari does NOT update 100dvh on keyboard; visualViewport.height does). Hook returns dynamic height + sets --keyboard-aware-height CSS var on :root. - Add 4 unit tests (vv available / sets CSS var / fallback / cleanup) — pass. - Replace h-screen → h-dvh in DesktopLayout + MobileLayout (D-02). - Integrate useVisualViewportHeight() side-effect call into 5 mobile components (MobileFiltersDrawer, MobileTimeSelectorSheet, MobileResultsSheet, MobileZoneCard, MobileSearchBar). - Add maxHeight: 'calc(var(--keyboard-aware-height, 100dvh) - 80px)' inline style to the 4 vaul Drawer.Content surfaces; wrap MobileSearchBar overlay height in same CSS var. - Preserve CO-02 single-snap [0.92] for MobileResultsSheet (D-06).
…ard (RESP-06,07) - src/index.css: add .map-controls-shifted-container [class*=ymaps3-controls] rule reading var(--bottom-sheet-offset, 20px) with 200ms ease transition. YMapControls does NOT accept className prop (typed reactify wrapper from @yandex/ymaps3-types), so parent-div selector-fallback is used. - MapCanvas.tsx: wrap inner div with class map-controls-shifted-container. - MobileLayout.tsx: useEffect sets --bottom-sheet-offset to calc(92vh + 20px) when ANY of filtersOpen/timeSheetOpen/resultsSheetOpen/selectedZoneId is active; default 20px otherwise. - eslint.config.js: add no-restricted-syntax rules blocking h-screen / min-h-screen / max-h-screen in className= AND any 100vh literal (D-07 regression guard). Verified rule fires on temp test file. - tests/e2e/tap-targets.spec.ts: Playwright runtime test asserting all buttons/links on /map iPhone 13 viewport have computed bbox >=44x44 (WCAG 2.5.5). Skips on ymaps3 CDN failure (Phase 3 known blocker) consistent with phase4-smoke.spec.ts pattern. Note: eslint-plugin-tailwindcss is NOT used — research finding: package does NOT support Tailwind 4 (issue #325 open). Playwright runtime test is the sole tap-target enforcement mechanism.
- Install sonner@^2.0.7 as runtime dependency (D-19) via --legacy-peer-deps - Extend env.ts Zod schema: VITE_SHARED_SHELL_URL (D-09) + VITE_API_MODE (D-15) - Create brand-tokens.ts: single source of truth (D-12) — green/amber/neutral/semantic - Append @theme directive to index.css (Tailwind 4 native, after Plan 05-01 .map-controls-shifted) - Update shared/config barrel to re-export brand - Document all 5 env vars in .env.example incl. localhost limitation warning (Pitfall 4)
…1..03)
- Replace throwing stub with TanStack Query + apiClient.get('/auth/me') (D-08, D-09)
- Map AuthMeResponse -> User (display_name = full_name ?? email)
- Localhost guard: console.warn when VITE_AUTH_MODE=shared on localhost (Pitfall 4)
- 4 vitest tests: 200 happy path, full_name=null fallback, 401 unauthenticated,
W-1 fix static source-content assertion via Vite ?raw import (replaces placebo)
- Test 4 uses ?raw instead of node:fs/__dirname (app tsconfig has no node types)
…06; UX-05,06) - AuthListener: 401 CustomEvent listener (D-10) — mock=invalidate+warning toast, shared=error toast + 2s redirect to /login?return=... - AppProviders: mount Toaster (zIndex 100, Pitfall 2 — above vaul z-50) inside AuthListener inside QueryProvider; AuthReady wraps children unchanged - Toast.tsx: thin sonner re-export (D-13) — vendor-swap = single-file change - Banner.tsx: in-drawer error primitive (D-13) — 4 variants, 44x44 dismiss button - StubHeader.tsx: returns null in shared mode (D-14, INTEG-06) - Update barrels: providers/index.ts re-exports AuthListener, ui/index.ts adds 4
- main.tsx: gate MSW worker registration on VITE_API_MODE (independent from VITE_AUTH_MODE per D-15) — enables 4-combo testing of API/auth modes; default mock when env unset - playwright.real-api.config.ts: dedicated config (NOT in default CI), testMatch pinned to real-api.spec.ts, HTML report to phase-05-uat/ - tests/e2e/real-api.spec.ts: 7 shape-only smoke tests covering all 6 endpoints (GET /zones, /zones/<id>, /occupancy, /forecasts; POST /routing/search, /routing/new) + D-17 combined-filter probe - package.json: test:e2e:real-api script via cross-env (Windows-portable) - cross-env@^7.0.3 added as devDep (B-2 ownership: Plan 05-04 must grep-guard before re-installing) - ambient process declaration in spec avoids polluting app tsconfig with node types (mirrors Plan 05-02 W-1 approach)
…D-17/D-18) - New section «Phase 5 D-17 verification protocol» appended (Phase 2 baseline preserved unchanged) - 7-row verification status table (one row per filter), all marked unverified — Plan 05-05 UAT fills statuses from real-api smoke - 5-value status legend (unverified/accepted/degraded/rejected/ client-only-fallback) clarifies action for each outcome - Per-filter individual curl probe commands documented for offending- param identification when combined GET fails - D-18 conditional normalizer note: normalizers.ts created ONLY if smoke shows shape divergence (avoid speculative dead code) - phase-05-uat/real-api-smoke.log artefact structure specified
…staleTime (D-29 NFR-01 + D-32 NFR-04) - Install audit devDeps: @axe-core/playwright, rollup-plugin-visualizer^6.0.4 (resolved ^6.0.11), size-limit^11, @size-limit/preset-app, ts-morph; cross-env already present (B-2 guard verified) - Enable noUncheckedIndexedAccess + exactOptionalPropertyTypes + noImplicitOverride + noImplicitReturns in tsconfig.app.json AFTER dry-run (107 errors → 0; fixed in batch across 14 files: array-access non-null assertions, conditional spread for optional props, defensive guards in zoneCentroid) - ESLint: @typescript-eslint/no-explicit-any: error blocks any in new code (existing code clean) - Mode-aware staleTime in zone.queries.ts (D-32 NFR-04 — I-1 fix moved here from 05-03): /zones (now) → 30s /occupancy (past) → 300s (5min, history immutable) /forecasts (future) → 60s (decay) /zones/<id> (now) → 60s /occupancy view=card → 300s /forecasts view=card → 60s - WTPCTAButton.test.tsx fixed (deferred from 05-01): mock navigator.permissions + findByText for async handleClick
…o (D-22/23/24/31/33 NFR-03/05/06) - vite.config.ts: rollup-plugin-visualizer (BUILD_ANALYZE=1) + manualChunks splitting: vendor-react (react+react-dom+scheduler+react-error-boundary co-located per Pitfall 10 TDZ), vendor-tanstack, vendor-state, vendor-ui (vaul+radix), vendor-icons (lucide), vendor-misc - .size-limit.json: 5 hard-fail budgets (initial<250KB, vendor-react<100KB, etc.); all pass: initial=21.65KB, vendor-react=60.89KB, vendor-tanstack=15.94KB, vendor-ui=17.69KB, vendor-icons=2.55KB - package.json: build:analyze + size scripts (cross-env from 05-03 reused per B-2) - nginx.conf: Content-Security-Policy verbatim from Yandex docs (script/connect/style/img/worker/frame-ancestors) + X-Content-Type-Options, X-Frame-Options, Referrer-Policy - index.html: csp=202512 migration param appended to Yandex CDN URL (Pitfall 12) - 4 widgets wrapped in React.memo (D-31 / I-3 fix incl. ParallelZoneLayer): ZoneLayer, RoutePreviewLayer, ParallelZoneLayer, DesktopResultsPanel
…s docs (D-30/34 NFR-02/07) - OfflineBanner via @tanstack/react-query onlineManager.subscribe (NOT navigator.onLine direct read per Pitfall 8 — Chrome bug) - Toast feedback through @/shared/ui wrapper (Plan 05-02 ingress): error 'Нет соединения' (id='offline', duration:Infinity) + success 'Соединение восстановлено' on reconnect - Mounted in AppProviders sibling to Toaster, inside AuthListener - Exported from app/providers/index.ts barrel - docs/fsd-exceptions.md documents 2 known cross-layer imports (ZoneCard→MapCanvas, useFilteredZones→filter-zones) — NFR-02 reviewer trail - Security grep audit (D-33): no token leakage in console.*, no token in localStorage, no dangerouslySetInnerHTML usage
…11y docs (D-21/25/27/28/35) - tests/e2e/a11y.spec.ts: axe-core scan over 4 flows (/map, /map?sel=42, /map?from=&dest=, /map?route=1), critical=0 blocks merge per D-26, serious console.warn for backlog (W-2 fix: no fs imports/writes) - tests/e2e/atomic-state.spec.ts: NFR-08 verification — 2 tests 1. parallel filter+time+zone change → no runtime errors 2. rapid filter toggle → AbortController cascade verifies completedRequests ≤ 2 (I-2 fix: tightened heuristic with rationale comment) - tests/unit/no-silent-failures.spec.ts: ts-morph AST audit asserts every useQuery/useMutation has onError/throwOnError; allowlist for queries that propagate error to caller (auth adapters, address suggest/resolve, zone/routing queries, user profile) - ambient declare process per Plan 05-02/03 minimal-surface pattern (no @types/node pollution) - 4 docs: a11y-backlog.md (placeholder for v1.x), a11y-keyboard-walkthrough.md (10-step manual scenario per D-27), a11y-colorblind-audit.md (5 vision-mode test matrix per D-28)
- web-map/docs/uat-flows-checklist.md: 12 manual flow steps (10 + VK/TG D-37/D-38) - web-map/docs/uat-matrix.md: required + optional device list with status table - Templates per D-36 — owner = Илья Р. (real-device tester); Claude prepares - Pass criteria: all 10 flows on iPhone iOS17+ Safari, Android 14+ Chrome, VK/TG webview
- Phase 5 deliverables: RESP-01..07, INTEG-01..06, A11Y-06, UX-05/06, NFR-01..08 - Documents ALL 24 Phase 5 requirements with implementation note - Lists known limitations + v1.x deferrals (Misha-shell, UI-kit, Lighthouse perf) - Notes default Playwright headless ymaps3 CDN limitation; UAT delegated - Phase 5 verification artifacts archived in .planning/phases/05-.../uat/
Mock /routing/search возвращал inactive (is_active=false) zones в кандидатах ranking → tap на парковку из ResultsPanel → ZoneCard показывала empty-state 'Зона неактивна в этот период' вместо контента. Server design assumption (applyClientCandidateFilters comment): RouteCandidate не имеет is_active поля — server должен ВСЕГДА возвращать только active+public parkings. Mock теперь mirror это поведение через ALWAYS-ON фильтр в rankCandidates. Affected: ResultsPanel ranked list, MobileResultsSheet, MobileZoneCard handoff flow. Frontend изменений не требует — applyClientCandidateFilters уже учитывает этот контракт.
Root cause: Phase 2 D-06 specified snapPoints=[0.4, 0.85], но vaul snap math требует drawer height >= largestSnap × viewport (≥792px на iPhone 14 Pro Max). Реальный content (header+tags+button ~408px) намного меньше — vaul применяет transform translateY(559px) который пушит drawer ENTIRELY off-screen. Карточка рендерится в DOM (verified: data-state=open, content visible), но визуально не видна. Тот же bug был в Phase 4 MobileResultsSheet — решился single-snap [0.92] (CO-02). Применяем тот же pattern: drawer открывается на 92% экрана, drag-down dismiss. Preview-режим [0.4] deferred to v1.x design pass per CO-02 protocol. Affected mobile flow: tap parking → ResultsSheet close → MobileZoneCard open. Все 294/294 tests pass.
User feedback: drawer открывался на 92dvh с большим пустым пространством под кнопкой «Построить маршрут»; drag handle не работал из-за vaul snap math. Решение: убрать snapPoints/activeSnapPoint полностью. vaul без snap-points auto-fit'нет drawer на natural content height — drawer открывается ровно до кнопки + 15px (pb-[15px]) внизу. Drag-down dismiss работает нативно через vaul handle. max-height ограничивает экстремальные случаи viewport-safe-area. Снимает: hot-fix 56a7fa3 ([0.92] snap который тоже не fit'ил content корректно). Ничего не ломает existing tests (294/294 pass).
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.