Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
43aed60
Merge pull request #5 from ParkTrack-Project/development-1
Lukramancer May 1, 2026
4cfb570
fix: build arguments in docker
Lukramancer May 3, 2026
70979b0
feat(05-01): add useVisualViewportHeight + h-dvh swap (RESP-01,02,05)
MrGoldSky May 3, 2026
f0159ee
feat(05-01): map-controls offset + Playwright tap-targets + ESLint gu…
MrGoldSky May 3, 2026
6803c01
feat(05-02): brand-tokens + Sonner install + env extension (INTEG-04)
MrGoldSky May 3, 2026
e42c94d
feat(05-02): SharedAuthAdapter implementation + 4 unit tests (INTEG-0…
MrGoldSky May 3, 2026
ad4e8b7
feat(05-02): AuthListener + Sonner Toaster + UI primitives (INTEG-05,…
MrGoldSky May 3, 2026
7c83bb1
feat(05-03): conditional MSW + real-api Playwright smoke (D-15/D-16)
MrGoldSky May 3, 2026
635c94e
docs(05-03): filters-contract.md Phase 5 D-17 verification protocol (…
MrGoldSky May 3, 2026
d961823
feat(05-04): TS strict flags + ESLint no-explicit-any + per-endpoint …
MrGoldSky May 3, 2026
17b50ef
feat(05-04): manualChunks + size-limit + CSP + csp=202512 + React.mem…
MrGoldSky May 3, 2026
05047cc
feat(05-04): OfflineBanner via TanStack onlineManager + FSD exception…
MrGoldSky May 3, 2026
93f2567
test(05-04): axe E2E + atomic-state E2E + no-silent-failures unit + a…
MrGoldSky May 3, 2026
b9be83b
docs(05-05): add UAT flows checklist + device matrix templates
MrGoldSky May 3, 2026
fb1bb3d
docs(05-05): add v1.0.0-mvp CHANGELOG release notes
MrGoldSky May 3, 2026
8052c69
fix(05): exclude inactive+private zones from /routing/search ranking
MrGoldSky May 3, 2026
56a7fa3
fix(05): MobileZoneCard single-snap [0.92] — drawer was off-screen
MrGoldSky May 3, 2026
8c7c165
fix(05): MobileZoneCard auto-fit content + 15px bottom padding
MrGoldSky May 3, 2026
abd8ae8
Merge pull request #3 from ParkTrack-Project/feat/mvp-rewrite
Lukramancer May 4, 2026
367c576
Revert "ЧИТИРИ"
Lukramancer May 4, 2026
6fa1c90
Merge pull request #8 from ParkTrack-Project/revert-3-feat/mvp-rewrite
Lukramancer May 4, 2026
3d42e8e
fix: build args
Lukramancer May 5, 2026
3e288c7
ci-cd: update deploy.yml
Lukramancer May 6, 2026
1163001
Merge remote-tracking branch 'origin/main'
nawinds May 9, 2026
aa42815
bugfix: VITE_API_MODE added to deploy workflow
nawinds May 9, 2026
7ba1bea
Reapply "ЧИТИРИ"
nawinds May 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# Yandex Maps API key (referer-restricted на parktrack.live и localhost)
# Получить: https://developer.tech.yandex.ru/services/3
VITE_YMAP_KEY=your-yandex-maps-v3-key-here

# Auth-режим: 'mock' (MSW) для DEV/staging, 'shared' для production с каркасом Миши.
# WARNING: VITE_AUTH_MODE=shared работает ТОЛЬКО на parktrack.live subdomains
# (cookie Domain=.parktrack.live недоступна на localhost — см. Pitfall 4 в Phase 5 RESEARCH).
VITE_AUTH_MODE=mock

# API-режим: 'mock' (MSW handlers) или 'real' (api.parktrack.live).
# Независим от VITE_AUTH_MODE — можно тестировать combo (real-API + mock-auth и наоборот).
VITE_API_MODE=mock

# Базовый URL backend API (Никита).
VITE_API_BASE_URL=https://api.parktrack.live

# URL общего shell-каркаса Миши (для 401 redirect: ${VITE_SHARED_SHELL_URL}/login?return=...).
# Применяется только когда VITE_AUTH_MODE=shared.
VITE_SHARED_SHELL_URL=https://parktrack.live
69 changes: 69 additions & 0 deletions .github/workflows/build-and-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Build and push Docker image

on:
push:
branches: ["main", "development"]
pull_request:
branches: ["main"]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to GHCR
if: env.REGISTRY == 'ghcr.io'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Generic path for future non-GHCR registries
- name: Log in to external registry
if: env.REGISTRY != 'ghcr.io'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=development,enable=${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }}
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=sha,format=short

- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
build-args: |
VITE_API_BASE_URL=${{ vars.VITE_API_BASE_URL }}
VITE_YMAP_KEY=${{ secrets.VITE_YMAP_KEY }}
VITE_AUTH_MODE=${{ vars.VITE_AUTH_MODE }}
VITE_SHARED_SHELL_URL=${{ vars.VITE_SHARED_SHELL_URL }}
VITE_API_MODE=${{ vars.VITE_API_MODE }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
29 changes: 10 additions & 19 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,16 @@ jobs:
cache: 'npm'

- name: Install dependencies
run: |
if [ -f package-lock.json ]; then
npm ci
else
npm i
fi

# (Опционально) Отдельная проверка типов, не ломает пайплайн
- name: Type check (non-blocking)
run: |
if npx --yes tsc -v >/dev/null 2>&1; then
npx --yes tsc --noEmit || echo "⚠️ TypeScript errors found (not blocking deploy)"
else
echo "tsc not found, skipping type check"
fi
run: npm ci --legacy-peer-deps

# ВАЖНО: Сборка напрямую через Vite, минуя скрипт `build` с tsc -b
- name: Build Vite project (skip tsc)
run: npx --yes vite build
- name: Build project
run: npm run build
env:
VITE_YMAP_KEY: ${{ secrets.VITE_YMAP_KEY }}
VITE_API_BASE_URL: ${{ vars.VITE_API_BASE_URL }}
VITE_AUTH_MODE: ${{ vars.VITE_AUTH_MODE }}
VITE_API_MODE: ${{ vars.VITE_API_MODE }}
VITE_SHARED_SHELL_URL: ${{ vars.VITE_SHARED_SHELL_URL }}

- name: Setup SSH
run: |
Expand All @@ -54,4 +45,4 @@ jobs:
rsync -az --delete \
-e "ssh" \
dist/ \
"${{ secrets.USERNAME }}@${{ secrets.SERVER_IP }}:${{ secrets.PROJECT_PATH }}/"
"${{ secrets.USERNAME }}@${{ secrets.SERVER_IP }}:${{ secrets.DIST_PATH }}/"
27 changes: 27 additions & 0 deletions .size-limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[
{ "name": "Initial app code", "path": "dist/assets/index-*.js", "limit": "250 KB", "gzip": true },
{
"name": "vendor-react chunk",
"path": "dist/assets/vendor-react-*.js",
"limit": "100 KB",
"gzip": true
},
{
"name": "vendor-tanstack chunk",
"path": "dist/assets/vendor-tanstack-*.js",
"limit": "60 KB",
"gzip": true
},
{
"name": "vendor-ui chunk (vaul + radix)",
"path": "dist/assets/vendor-ui-*.js",
"limit": "50 KB",
"gzip": true
},
{
"name": "vendor-icons chunk (lucide-react)",
"path": "dist/assets/vendor-icons-*.js",
"limit": "30 KB",
"gzip": true
}
]
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Changelog

## [1.0.0-mvp] — Phase 5 verification complete

Final MVP release. Merge from `feat/mvp-rewrite` → `main`.

### Added (Phase 5)

- **Responsive polish (RESP-01..07):** `useVisualViewportHeight` hook for mobile keyboard handling, `h-dvh` migration, `--bottom-sheet-offset` CSS var system, Playwright runtime tap-target test (>=44x44), ESLint guard `no-100vh`.
- **Integration readiness (INTEG-01..06):** Working SharedAuthAdapter (code-ready; real smoke deferred to post-Misha integration), AuthListener for 401 CustomEvent (toast + redirect), Sonner toast system with vaul-compatible z-index, `brand-tokens.ts` single source of truth, StubHeader / Toast / Banner primitives, `.env.example` complete.
- **Real-API toggle (INTEG-04):** `VITE_API_MODE=mock|real` env var, dedicated Playwright `real-api.spec.ts` + config (manual run via `npm run test:e2e:real-api`), filters-contract.md verification protocol.
- **NFR audit (NFR-01..08):** TypeScript strict (noUncheckedIndexedAccess + exactOptionalPropertyTypes + noImplicitOverride + noImplicitReturns), ESLint `no-explicit-any: error`, Vite `manualChunks` (vendor-react / vendor-tanstack / vendor-state / vendor-ui / vendor-icons / vendor-misc), `size-limit` budgets (CI hard-fail), per-endpoint TanStack staleTime tuning per D-32 (NFR-04), CSP header in nginx (verbatim from Yandex docs incl. `csp=202512` migration param), security grep audit, OfflineBanner via TanStack `onlineManager`, atomic-state E2E.
- **A11Y (A11Y-06):** axe-core E2E for 4 critical flows (CRITICAL===0 gate; serious/moderate to backlog), keyboard walkthrough doc, colorblind audit doc.
- **UAT artifacts:** Real-device matrix + 10-step flow checklist + cluster fps measurement methodology + merge-readiness checklist.

### Changed

- 4 widgets wrapped in `React.memo` (NFR-03): ZoneLayer, ParallelZoneLayer, RoutePreviewLayer, DesktopResultsPanel.
- `index.html` Yandex CDN URL appends `&csp=202512` (mandatory until April 2026).
- `shared-adapter.ts` no longer throws — fully implements `AuthAdapter` contract via `/auth/me` cookie call.
- Mode-aware TanStack staleTime per endpoint (NFR-04): `/zones` (now)=30s, `/occupancy` (past)=300s, `/forecasts` (future)=60s, `/zones/:id` (now)=60s.
- ESLint `no-restricted-syntax` blocks `h-screen` / `100vh` regressions (RESP-02 enforcement).

### Carry-over from Phase 4

- **ROUTE-08** real-device deeplink test: covered by UAT flows step 9 + VK/TG step 11-12.

### Known limitations / Deferred to v1.x

- Real Misha-shell smoke: blocked by Misha — deferred to post-MVP integration ticket.
- Real Misha-UI-kit replacement: blocked — placeholder primitives in `shared/ui/`; migration path is single-file barrel swap.
- `eslint-plugin-tailwindcss` for tap-target enforcement: package does NOT support Tailwind 4 (issue #325) — replaced by Playwright runtime test.
- `MobileResultsSheet` two-snap [0.4, 0.85]: Phase 4 CO-02 deferred; if UAT shows UX problem → v1.x.
- VK/TG in-app browser yandexnavi:// behavior: 2.5s fallback acceptable; deeper UX fixes if found in UAT → v1.x.
- Lighthouse perf-score >90: functional NFR audit done; full perf optimization (image lazy-loading, font subsetting, route-based code-split) → v1.x.
- axe serious/moderate findings: backlog in `web-map/docs/a11y-backlog.md`.
- Sentry / monitoring integration: post-MVP integration ticket.
- Default Playwright E2E suite (smoke / map / filters / phase4-smoke / time-selector etc.) currently fails in headless Chrome due to ymaps3 CDN blocked in headless mode (Phase 3 known blocker per STATE.md). Default `npx playwright test` reports many failures; functional verification is delegated to manual UAT flows on real devices in Plan 05-05. The dedicated `tap-targets.spec.ts` and `a11y.spec.ts` use the documented skip-on-ymaps3-failure pattern.
19 changes: 19 additions & 0 deletions docs/a11y-backlog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# A11Y backlog (serious + moderate)

Phase 5 D-26: critical issues block merge; serious/moderate accumulate here for v1.x cleanup.

## How to fill this list

1. Run `cd web-map && npx playwright test tests/e2e/a11y.spec.ts`
2. axe results console-warn lines starting with `[a11y backlog]` indicate serious findings per flow
3. Open the Playwright HTML report: `npx playwright show-report`
4. For each serious violation: id, impact, target nodes, recommendation
5. Add to «Open issues» section below as: `- [ ] {flow} / {axe-rule-id} / {nodes count} / {brief}`

## Open issues

(To be filled by Plan 05-05 UAT and v1.x review.)

## Closed (fixed in Phase 5)

- (Critical issues resolved at Plan 05-04 commit, list here as «- {axe-rule-id} fixed by {file}» if any encountered.)
38 changes: 38 additions & 0 deletions docs/a11y-colorblind-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# A11Y colorblind audit (Phase 5 D-28)

Verify all 5 zone semantic states (red / yellow / light-green / dark-green / grey per ZONE-02) are distinguishable under color vision deficiencies.

## Setup

1. Open Chrome DevTools → ⋮ → More tools → Rendering
2. Scroll to «Emulate vision deficiencies»

## Test matrix

| Vision mode | Expected outcome |
| -------------- | ----------------------------------------------------------------------------- |
| None | All 5 colors visually distinct |
| Achromatopsia | Distinguishable via free_count badge (Phase 2 D-02 redundant encoding) |
| Protanopia | Red/dark-green may merge → free_count badge differentiates |
| Deuteranopia | Similar to Protanopia → free_count badge differentiates |
| Tritanopia | Yellow/green pair may shift → free_count badge differentiates |
| Blurred vision | Color still distinguishable; badge readability tested at zoom_level=14+ |

## Test procedure

1. Open `/map` with viewport showing a mix of zone states (use MSW handler with `?count=50` if needed for variety)
2. For each vision mode in matrix:
a. Activate emulation
b. Take a screenshot of the visible map area
c. Save as `phase-05-uat/colorblind-{mode}.png`
d. Verify each of 5 states identifiable (color OR badge)
3. Pass: all 5 states identifiable in all modes via at least one channel (color or badge)

## Known mitigations

- Phase 2 D-02 redundant encoding: every zone has free_count badge (number) overlaid; even at full color blindness the digit reveals state.
- Phase 2 D-01 zone palette chosen to be colorblind-safe (verified by viz4all proportional dichromat simulation during research).

## Failures

(Filled by Plan 05-05 UAT.)
34 changes: 34 additions & 0 deletions docs/a11y-keyboard-walkthrough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# A11Y manual keyboard walkthrough (Phase 5 D-27)

Manual test scenario for full keyboard navigation. Run on every Phase 5 verification + every regression bug fix touching focus order.

## Setup

- Browser: Chrome stable
- Window: desktop viewport (≥1024px) for first pass; iPhone 13 emulation for second pass
- Disable mouse temporarily (alternative: use only Tab/Shift+Tab/Enter/Space/Esc/Arrow keys)

## Walkthrough steps

1. Tab from URL bar → first focus lands on TimeSelectorPopover trigger button (top-4 left-4 cluster); visible focus ring present
2. Tab → WTPCTAButton («Где припарковаться?») receives focus; press Enter → pre-flight modal opens
3. Inside pre-flight: Tab to «Разрешить геолокацию» button; Esc closes modal, focus returns to WTPCTAButton (focus restoration)
4. Tab → SearchBar input; type «Невский» → autosuggest list appears; ArrowDown navigates suggestions; Enter selects
5. Tab → DesktopFiltersPopover trigger; Enter → popover opens; Tab cycles through 7 filters (chip-toggle, sliders, location-type checkbox group); Esc closes
6. (Mouse-only) Click a zone on map → ZoneCard side panel opens; Esc closes (focus returns to map area or last focused element)
7. Tab → ResultsPanel item (when ?from set); Enter or Space selects zone + opens card
8. Tab → «Построить маршрут» in ZoneCard; Enter → mutation runs, RoutePreviewLayer renders; Tab → «В путь» button; Enter → deeplink menu opens
9. Tab through deeplink menu options (3 items: Я.Навигатор / Я.Карты web / Google Maps); Enter selects; deeplink launches
10. (Mobile pass) Open MobileResultsButton bottom-center chip via Enter when focused; vaul Drawer opens; Tab cycles within drawer (focus trap); Esc closes drawer

## Pass criteria

- All steps completable without mouse
- Focus ring visible at every step (no «invisible focus»)
- Esc always closes overlays without exiting the app
- Tab/Shift+Tab order matches visual top-to-bottom + left-to-right reading order

## Known limitations

- Map canvas is intentionally NOT keyboard-accessible (Phase 2 D-17 — keyboard users navigate via filter/list/card; map is purely visual). This matches WCAG SC 2.1.1 «Keyboard» exemption for primary visual content.
- Yandex zoom controls (+/-) are within map canvas — also not in Tab order.
55 changes: 55 additions & 0 deletions docs/filters-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,58 @@ UI хранит **видимые** типы (например `['street', 'yard'
1. Прогнать каждый из 7 фильтров вручную → проверить, что response-size меняется
2. Если для какого-то параметра API вернёт 400/422 — пометить «client-only» в этой таблице, удалить из buildServerQuery, оставить в applyClientFilters
3. Если появятся новые server params (`min_free_count_relative` и т.п.) — обновить таблицу и buildServerQuery

## Phase 5 D-17 verification protocol

Before flipping `VITE_API_MODE=real` for production:

1. Run `npm run test:e2e:real-api` (Plan 05-03) — the «Filters: GET /zones with all 7 filter params» test asserts the combined-params GET returns 200.
2. If combined GET returns 400/422 → real API does NOT support one of the 7 server params. Identify the offending param via individual smoke (one filter at a time):

```bash
curl "$VITE_API_BASE_URL/zones?bbox=$BBOX&view=map&min_free_count=1"
curl "$VITE_API_BASE_URL/zones?bbox=$BBOX&view=map&min_confidence=0.5"
curl "$VITE_API_BASE_URL/zones?bbox=$BBOX&view=map&max_pay=200"
curl "$VITE_API_BASE_URL/zones?bbox=$BBOX&view=map&include_private=false"
curl "$VITE_API_BASE_URL/zones?bbox=$BBOX&view=map&include_accessible=false"
curl "$VITE_API_BASE_URL/zones?bbox=$BBOX&view=map&hide_location_types=open_lot,underground"
curl "$VITE_API_BASE_URL/zones?bbox=$BBOX&view=map&is_active=true"
```

3. Update the verification status table below with the result for each param.
4. For any param marked `rejected`: edit `web-map/src/features/filter-zones/lib/buildServerQuery.ts` to NOT emit that param to server; corresponding client predicate in `applyClientFilters.ts` becomes the sole gate.
5. Append the smoke artefact to `phase-05-uat/real-api-smoke.log` (see structure below).

### Verification status (filled by Plan 05-05 UAT)

| UI filter | Server param | Real-API smoke status | Action if unsupported |
| -------------- | -------------------- | --------------------- | -------------------------------------- |
| hideNoFree | `min_free_count` | unverified | Drop from buildServerQuery |
| minConf | `min_confidence` | unverified | Already client-side too (safety-net) |
| maxPay | `max_pay` | unverified | Already client-side too (safety-net) |
| hidePrivate | `include_private` | unverified | Drop from buildServerQuery |
| hideAccessible | `include_accessible` | unverified | Drop from buildServerQuery |
| locationType | `hide_location_types`| unverified | Drop, locationType remains client-only |
| hideInactive | `is_active` | unverified | Drop from buildServerQuery |

Status legend:

- `unverified` — not yet smoke-tested against real API (initial state at Plan 05-03 close)
- `accepted` — real API returns 200 with this param and visibly filters response
- `degraded` — real API accepts but ignores; client predicate still works as safety-net
- `rejected` — real API returns 4xx; param removed from buildServerQuery, client-only fallback engages
- `client-only-fallback` — explicit choice to keep predicate client-side regardless of server support (e.g. when reliable filtering is required even on partial backend coverage)

### Phase 5 D-18 normalizer conditional

If real-API `/zones` response shape differs from our `web-map/src/entities/zone/model/zone.types.ts` `Zone` interface (e.g. missing field, renamed key, different enum values), Plan 05-05 should create `web-map/src/entities/zone/api/normalizers.ts` exporting `normalizeZone(raw): Zone`. ALL raw→domain mapping happens there — no scattered casts in widgets/features. If shapes match → no normalizer needed (D-18: minimize dead code).

Smoke artifacts log: `phase-05-uat/real-api-smoke.log` should record:

- Endpoint URL (with query string)
- HTTP status code
- First 200 chars of response body
- Shape diff vs our `Zone` / `RouteCandidate` / `Route` interface (if applicable)
- Date and `git rev-parse --short HEAD` of web-map at smoke time

Cross-link: Plan 05-03 only sets up the protocol; Plan 05-05 UAT actually runs `npm run test:e2e:real-api` against the live `api.parktrack.live` and fills in the table above.
Loading
Loading