Журнал версий проекта Relocation Companion 2027. Каждая запись — одна логическая правка. Нумерация — 0.XXX.
Для развёрнутых описаний см. CHANGELOG.md. Для текущего плана работ — BACKLOG.md. Для навигации по файлам — FILES.md.
| Версия | Дата | Тип | Кратко |
|---|---|---|---|
| 0.001 | 2026-05-16 | docs | Создан FILES.md — индекс-навигация: дерево, порядок загрузки, экспорты window.*, конвенции, рецепты типовых задач |
| 0.002 | 2026-05-16 | docs | Создан BACKLOG.md — план итерации, ограничения по знанию (FX/визы), 5 этапов работ |
| 0.003 | 2026-05-16 | data | Создан src/data/extras.js — window.FX (заглушка 95 ₽/$), BASKET_ITEMS (10 позиций), LOCATION_EXTRAS[id] (визы РФ + цены) + 4 новые азиатские локации: Дананг, Чиангмай, Пенанг, Санья |
| 0.004 | 2026-05-16 | data | Создан src/data/conditions.js — window.CONDITIONS (атопия, СРК, ринит, экзема рук), scoreLocationForCondition(), ссылки на PubMed |
| 0.005 | 2026-05-16 | styles | Создан src/styles/extras.css — изолированные стили для новых страниц (не перекрывают main.css) |
| 0.006 | 2026-05-16 | feature | Создана страница src/modules/basket.jsx — «Продуктовая корзина». USD основная + RUB мелким, визовые теги, сортировка по итогу |
| 0.007 | 2026-05-16 | feature | Создана страница src/modules/conditions.jsx — «Болезни и климат». Карточки, рейтинг локаций, ссылки на источники |
| 0.008 | 2026-05-16 | integration | Подключены новые data/styles/modules в index.html (+5 строк); добавлены nav-пункты 07/08 и Page-ключи в src/components/app.jsx (+4 строки) |
| 0.009 | 2026-05-16 | docs | Актуализирован FILES.md (новые файлы, экспорты extras.js и conditions.js) и BACKLOG.md (прогресс этапов 1–3 → ✅) |
| 0.010 | 2026-05-16 | audit | Верхнеуровневый аудит «где видны/не видны азиатские локации». Найдены 3 проблемы: persisted в decision.jsx, группировка по морю в atlas.jsx, disconnect FX в finance.jsx. Зафиксировано в BACKLOG как Этап 6 |
| 0.011 | 2026-05-16 | fix | src/modules/decision.jsx — useEffect на mount мерджит window.LOCATIONS в persisted locs. Все 15 городов теперь видны на странице «Сравнение локаций» |
| 0.012 | 2026-05-16 | fix | src/modules/finance.jsx — поле «Курс ₽/$» связано с window.FX: инициализация из FX, двусторонняя синхронизация, кнопка «⤴ Применить FX-курс», тег «заглушка», отображение asOf и источника |
| 0.013 | 2026-05-16 | docs | В BACKLOG.md добавлен 🩺 медицинский раздел: профиль пациентов с ICD-10, комментарии дерматолога мирового уровня (5 патогенетических осей атопии, ранжировка 15 локаций), эндокринолога (HPA, вит. D, йод, SAD, gut-microbiota-brain axis). Добавлены задачи 7.3–7.6 |
| 0.014 | 2026-05-16 | docs | Создан CHANGELOG.md — разбивка работ на 3 сессии в формате Keep a Changelog (Added / Changed / Fixed / Audit) |
| 0.015 | 2026-05-16 | docs | Создан VERSIONS.md — этот файл, журнал версий с нумерацией 0.001+ |
| 0.016 | 2026-05-19 | feature | Mobile-first PWA добавлен в app/ — параллельный entry point: 5 файлов (index.html, app.jsx, shell.jsx, primitives.jsx, styles.css) + 4 экрана (today, atlas, location, _stubs) + manifest.json. Tab-роутер, dark/light темы, safe-area, использует общий data layer и rc2027: префикс |
| 0.017 | 2026-05-19 | docs | Обновлён FILES.md: раздел «Две точки входа», подробное описание app/ с табличными экспортами всех экранов |
| 0.018 | 2026-05-19 | refactor | Объединение в одно приложение: mobile-first app/ поднят в корень. Корневой index.html теперь mobile-first PWA, старый desktop перенесён в legacy/index.html с правкой путей на ../src/*. manifest.json поднят в корень. vercel.json: добавлены кеш-правила для /app/* и manifest.json. Удалён дубль app/index.html |
| 0.019 | 2026-05-19 | feature | Mobile-redesign Basket: app/screens/basket.jsx — hero c FX+медианой, segmented сортировка, pill-фильтр (все/безвиз/ЕС/Азия), карточки городов с большим USD + RUB мелким + Bar относительно медианы + %-разница, bottom-sheet с детализацией 10 позиций и переходом в карточку города. Удалён stub из _stubs.jsx, добавлена IconBtn cart в app/screens/atlas.jsx TopBar для входа |
| 0.020 | 2026-05-19 | docs · SA | Pivot к публичному сервису: проект переориентирован на русскоязычных релокантов. Сохранены 3 memory-файла (project_pivot_to_service.md, project_privacy_architecture.md, feedback_workflow.md). Создан docs/DATA_MAP.md в роли системного аналитика: 7 категорий ЦА с 35+ подгруппами (соло/пары/семьи с детьми/пожилыми/животными/инвалидностью/IT/беженцы/LGBTQ/религиозные/риск-юрисдикции и т.д.), полная инвентаризация данных, PII-классификация, lifecycle, admin/debug procedure, negative list (что не собирается), 5 открытых вопросов |
| 0.021 | 2026-05-19 | docs · SA | Multi-profile архитектура зафиксирована в DATA_MAP §8.2: ключи rc2027:<profileId>:* с самого начала, UI скрыт пока 1 профиль. Создан docs/USER_STORIES.md: 4 personas (одинокий IT с кошкой, пара с детьми и атопией, женщина с пожилой мамой и собакой, человек в wheelchair), 18 user stories по 6 фазам пути, acceptance criteria для критичных, story map → экраны |
| 0.022 | 2026-05-19 | data | User Profile data layer: новая папка src/data/user-profile/ с 5 файлами: schema.js (25 audience tags, 5 категорий, member roles, age groups, diet tags, accessibility tags), default.js (createEmptyProfile), demo.js (createDemoProfile preset), storage.js (profileId-prefixed localStorage helpers + export/import/clear), context.jsx (ProfileProvider + useProfile() hook + reducer). app/app.jsx: <App /> обёрнут в <ProfileProvider>. index.html: подключение 5 новых файлов перед app/primitives.jsx |
| 0.023 | 2026-05-19 | feature | ScreenOnboarding — реальный многошаговый wizard (app/screens/onboarding.jsx). 8 шагов: welcome (+ кнопка демо), displayName, audienceTags (по 5 категориям), family (members + питомцы), health (diagnoses/diet/accessibility), finance (income/asset/rate), target (city + date), ready. Required: welcome → name → tags; остальные с skip. app/shell.jsx: PageResolver форсирует ScreenOnboarding при !_onboardingComplete && !_demo; BottomTabs скрываются. Удалён stub ScreenOnboarding |
| 0.024 | 2026-05-19 | refactor · deperson | Depersonalization 2 экранов: today.jsx — displayName / targetCity / originCity / targetDate теперь из useProfile(), climate-карточка показывает реальные показатели targetLoc, insight зависит от outputCity. location.jsx — score breakdown учитывает profile.weights если заданы; TabHealth фильтрует CONDITIONS по profile.health.conditions (если пусто — fallback на «общая информация»). atlas.jsx и basket.jsx уже generic, изменений не требуется |
| 0.025 | 2026-05-19 | feature | ScreenProfile — app/screens/profile.jsx на 6 секций: hero (имя + аватар-инициал + бейдж демо), Identity (targetDate / origin / target), Audience tags (с edit-sheet), Household (members + питомцы), Health (диагнозы / диета / доступность), Finance (income / asset / rate), Privacy (телеметрия + экспорт/импорт JSON + force-completeOnboarding), Profile switcher (виден когда профилей >1 + кнопка добавить), Danger zone (удалить все). app/shell.jsx: корневой экран таба me теперь <ScreenProfile />. Удалены stubs ScreenMe и ScreenProfile из _stubs.jsx |
| 0.026 | 2026-05-19 | feature · migrate | ScreenRoadmap — app/screens/roadmap.jsx. Mobile-first redesign Kanban → вертикальный список с pill-фильтром lane + segmented status (активные/все/готовые). TaskCard с кастомным checkbox, tap-on-pencil → bottom-sheet edit (title/lane/due/deps/delete). FAB-кнопка + в TopBar открывает sheet добавления. Audience-aware: buildAudienceSuggestions(userTags) предлагает preset-задачи под family-pets (4), family-elderly (4), family-children (3), mobility-wheelchair (3), prof-it (2) — bulk-add через отдельный sheet. app/shell.jsx: корневой экран таба plan теперь <ScreenRoadmap />. Удалены stubs ScreenRoadmap и ScreenPlanHome |
| 0.028 | 2026-05-20 | feature · migrate | ScreenFinance — app/screens/finance.jsx. JTBD: снизить финансовую тревогу → runway + диапазон исходов. Hero: капитал / runway / savings% из profile.finance. Сценарии курса (оптим/базовый/пессим). Редактируемые капитал + курс с FX-sync. Бюджет (7 статей + bar-визуализация). Адаптивный Monte Carlo SVG (P10/P50/P90, 400 траекторий). Транзит капитала (SWIFT/USDT/золото) и ROI-калькулятор — в bottom-sheet. Явная кнопка «Сохранить в профиль» + window.FX sync. Финансовая mini-карточка в today.jsx. Стаб удалён. |
| 0.032 | 2026-05-20 | feature · migrate | ScreenChat — app/screens/chat.jsx. Tab root «Поиск / Компаньон»: bubble-чат интерфейс, fuzzy keyword search по QNA_SAMPLE + qna:custom (нормализация ё→е, слова ≥3 символа, boost по профилю), welcome-state с 6 quick-chips, ChatResultCard (expandable аккордеон с cat-badge + gastro borderLeft), Sheet добавления новой Q&A-записи (q+a+cat+src → localStorage). Ноль внешних зависимостей, работает офлайн. Stub ScreenChat удалён. |
| 0.040 | 2026-05-20 | feature · arch | Углублённое досье городов — архитектура для масштаба 100k+ юзеров. JSON-файлы вместо JS-глобалов: src/data/locations/kobuleti.json — 14 секций (overview, rent, realEstate, legal, banking, infra, medicine, food, pets, community, adaptation, costs, risks) с audience-aware фильтрацией по audienceTags из DATA_MAP §2 и verificationStatus для post-2025 данных. Манифест src/data/locations/index.json с версиями. src/data/location-loader.js — window.LocationLoader с LRU-кешем на 5 локаций, дедупликацией in-flight запросов, prefetch, cache: 'force-cache'. JSON Schema docs/schemas/location.v1.json — контракт для остальных 10 локаций. Подключение через 1 скрипт в index.html; города грузятся лениво только при открытии ScreenLocation. Initial bundle не растёт. |
| 0.041 | 2026-05-20 | data · tag | Тегирование Q&A локацией — добавлено поле loc: 'kobuleti' к 87 записям в src/data/qna.js, относящимся к Кобулети-специфичным секциям. Универсальные записи (мульти-страновые: Болгария, Черногория, Армения и т.д.) остаются без loc. Распределение по категориям: life=20, visa=9, home=9, med=8, psy=7, tax=6, capital=6, banks=6, tel=5, pets=4, auto=4, gastro=3. Это подготавливает контекстный поиск в ScreenChat и ScreenQnA (Шаг 4) — приоритизация результатов по текущему городу профиля. Общая структура entry: { cat, loc?, q, a, src, updated }. |
| 0.042 | 2026-05-20 | feature | ScreenLocation +5 расширенных табов — app/screens/location.jsx. Хук useLocationDeep(id) обращается к LocationLoader (sync peek + async fetch). Хелперы audienceMatch(item, userTags), severityTone(), VerifyBadge. 5 новых табов: жильё (rent + realEstate с порогом ВНЖ), легализация (visaFree + permits + taxes + critical-alert workPermit2026 + extradition), быт (internet plans + mobile + power outages + transport), сообщество (Telegram-чаты группами + школы по audience + церкви), риски (фильтр по audienceTags, сортировка по severity, mitigation, VerifyBadge). <QnaLink refs> — deep-link в чат с числом ответов. Tab pillrow с горизонтальным scroll. Graceful degradation: для городов без deep-досье показываются только базовые 6 табов. app/primitives.jsx: Card теперь принимает style prop с merge поверх padding. |
| 0.043 | 2026-05-20 | feature · loop | Контекстный поиск Q&A — замкнут Action Loop (Шаг 4 по AJTBD). app/shell.jsx: nav.switchTab(t, ctx) принимает транзитный контекст таба, nav.tabContext, nav.clearTabContext(). BottomTabs тоже через nav.switchTab — сбрасывают контекст при ручном переходе. app/screens/chat.jsx: chatSearch(q, qna, profile, locContext) — записи с item.loc === locContext получают boost +5 (выше категориальных, ниже текстовых совпадений). Активный loc: ручной override → nav.tabContext.loc → profile.targetCity. Чип-индикатор «по [Город] ×» в UI с возможностью снять. Auto-search через tabContext.autoSearch. app/screens/location.jsx: QnaLink теперь принимает loc + seed, каждый из 5 расширенных табов передаёт свою seed-фразу (жильё/легализация/быт/сообщество/риски). Smoke-тест: «виза внж» без ctx → score 4 global, с ctx kobuleti → score 9 kobuleti. Несуществующий ctx → graceful fallback на глобальный поиск. |
| 0.044 | 2026-05-20 | feature · loop | «В план» CTA из досье города → Roadmap (Шаг 5 AJTBD). app/screens/location.jsx: хук useAddToPlan() пишет в rc2027:tasks (тот же ключ что у ScreenRoadmap) с дедупликацией по title + _source. Хелпер deriveDue(effectiveDate, severity) строит due-строку формата Roadmap: 'действует' для просроченных дат, 'T-N мес' по сроку до даты, fallback по severity (critical→T-1, high→T-2, medium→T-3). Компонент <AddToPlanBtn draft={...}> реактивный (показывает «✓ в плане» если уже добавлено). 6 точек добавления: critical-alert workPermit2026 (lane:docs), каждый residencePermit (T-3 мес), extradition (T-6 мес), «найти жильё» в TabRent (lane:prop), critical+high риски в TabRisks (mapping risk→lane), школы в TabCommunity (T-4 мес). _source поле в задаче — для трассировки происхождения. 8/8 кейсов deriveDue пройдены. |
| 0.045 | 2026-05-20 | feature · loop · fix | targetCity синхронизация — замкнут Action Loop «Возвращаться» (Шаг 6 AJTBD). app/primitives.jsx: useLocationDeep(id) + audienceMatch(item, userTags) экспортированы в window.* — теперь доступны на всех экранах, не только ScreenLocation. app/screens/location.jsx: IconBtn name="target" в TopBar — тап делает текущий город целевым (profile.targetCity = id) или снимает. Pill ✓ ваш целевой в hero рядом с EU-бейджем. app/screens/today.jsx: новая карточка <TargetCityBrief> после климата — показывает топ-1 critical/high риск для аудитории профиля (audienceMatch) и счётчик задач по городу (_source.startsWith(targetCityId+':')); CTA в досье и в план. app/screens/roadmap.jsx: pill по [Город] · N сужает список задач только до city-specific (по _source). Найден и пофиксен критический баг сортировки рисков: sevOrder.critical === 0 → 0 || 4 === 4 (JS-ловушка) → критические риски попадали в конец. Заменено на ?? 4 в TabRisks и TargetCityBrief. Тест: prof-it → теперь критический work-permit-2026 в top, risk-jurisdiction → критическая экстрадиция. |
| 0.046 | 2026-05-20 | feature · trust | Trust signals — прозрачность данных (Шаг 7 AJTBD, Track 3). app/screens/location.jsx: новый компонент <TrustMeta> в hero — показывает «обновлено YYYY-MM-DD» + pill «N блоков требуют проверки» (счётчик verificationStatus === 'source-only' по rent/realEstate/medicine.insurance/legal.workPermit2026/risks). Хелпер countSourceOnly(deep). VerifyBadge уже на месте в TabRent (rent + realEstate), TabLegal (workPermit2026), TabRisks (каждый риск). app/screens/chat.jsx: ChatResultCard теперь показывает loc-метку (имя города) рядом с категорией; источник стал кликабельным — кнопка «источники →» открывает <Sheet> с детальной разбивкой. Новый компонент <SourceSheetBody> парсит item.src по разделителям + / , , распознаёт URL-фрагменты (regex ^https?:// или domain.tld/...), делает их кликабельными ссылками (target="_blank" rel="noopener"), плейн-текст оставляет как mono-копию. Disclaimer о выходе на внешние ресурсы. Babel-parse: все 6 затронутых JSX-файлов валидны. |
| 0.058 | 2026-05-27 | pwa · #30 | Phase 6 · #30 — PWA install prompt + Update delivery flow. Иконки: создан icons/icon.svg — буква «Р» серифным шрифтом (Lora) на тёмно-синем фоне #0E2034 + золотая акцент-точка #C9A961. Сгенерированы PNG через новый scripts/generate-icons.mjs (npm script generate:icons): icon-192, icon-512, icon-maskable-512 (с 20% safe-zone padding для Android adaptive), apple-touch-icon-180, favicon-32, favicon.svg. Установлен sharp в devDeps. Manifest: полноценный manifest.json — id, start_url=/?source=pwa, display_override, categories, 5 icons (включая maskable), 3 shortcuts (Атлас / AI / План для long-press на Android-иконке). Service Worker update flow: убран автоматический skipWaiting() из install — управление передано клиенту через postMessage. Bumped CACHE_VERSION 1→2. Добавлен BroadcastChannel('rc2027-sw') — архитектура готовности для future force-update (не активна). Pre-cache расширен 7 иконками. app/app.jsx: добавлены APP_VERSION='1.0.58' и APP_BUILD_DATE. SW-регистрация дополнена: updatefound listener устанавливает window.__swWaiting, listener fires __swListeners Set; controllerchange → reload (с защитой от двойного reload); visibilitychange + 60-мин setInterval для периодической проверки обновлений. Hooks в app/primitives.jsx: useServiceWorker() возвращает {needsUpdate, applyUpdate, checkForUpdate, getVersion}; useInstallPrompt() перехватывает beforeinstallprompt, детектит iOS/Android/desktop, отдаёт {canInstall, isInstalled, platform, prompt}. UI на Today: <UpdateAvailableCard> (с акцентной полоской, dismissible per-session, кнопки «Обновить сейчас»/«Позже»); <InstallPromptCard> (показывается после 3-го визита, dismiss на 30 дней, отдельная iOS-инструкция через Sheet «Поделиться → На экран Домой»). UI на Profile: <AppInfoSection> — версия, build date, SW версия, install status + кнопки «Проверить обновления»/«Установить»/«Применить обновление». vercel.json: /icons/* с Cache-Control: max-age=31536000, immutable (1 год). index.html: добавлены <link rel="icon"> (SVG + 32px), <link rel="apple-touch-icon">, <meta name="apple-mobile-web-app-title">. Локальный тест: все 7 иконок отдаются 200 OK; manifest валиден; JSX 37/0 ошибок. Стратегия доставки обновлений документирована: push в main → Vercel deploy → SW отдаётся с no-cache → клиент детектит новый SW → ставит в waiting → app показывает «Доступно обновление» → пользователь → SKIP_WAITING → reload. Прогресс Phase 6: 14/50 (Sprint 6 завершён). |
| 0.057 | 2026-05-26 | pwa · #29 | Phase 6 · #29 — Service Worker для offline-доступа. Создан service-worker.js (~180 строк): три стратегии кэширования адаптированы под CDN-React-без-бандлера архитектуру. Cache-first для app shell (HTML/CSS/JSX/JS своего origin) — мгновенный возврат повторных визитов; pre-cache 32 файлов через Promise.allSettled (если 1 файл не загрузился — install не падает). Stale-while-revalidate для JSON-досье городов (/src/data/locations/*.json кроме index) — баланс свежести и offline: пользователь видит данные мгновенно из кэша, в фоне идёт обновление. Cache-first с passive ingest для CDN (unpkg.com, fonts.googleapis.com, fonts.gstatic.com) — обходит CSP connect-src 'self': SW кэширует ответы которые инициированы тегами <script>/<link>, а не сам делает кросс-оригин fetch. Network-first для HTML-навигации с SPA-fallback на /index.html при offline. Версионирование: CACHE_VERSION = '1' → три кэша (rc2027-shell-v1, rc2027-dossier-v1, rc2027-cdn-v1). Activate-handler чистит старые версии. skipWaiting() + clients.claim() → обновление вступает в силу немедленно при загрузке нового SW. Регистрация в app/app.jsx на событии load с обработчиком updatefound. vercel.json: SW отдаётся с Cache-Control: no-cache, no-store, must-revalidate (всегда проверять обновление) + Service-Worker-Allowed: / (область действия); CSP дополнен worker-src 'self'. Локальный тест через python http.server: 32/32 pre-cache URL отдаются (~1.2 MB общий объём; qna.js 1.1 MB — основной вес). Прогресс Phase 6: 13/50 (#22, #23, #1, #36, #37, #39, #9, #10, #11, #24, #26, #28, #29). |
| 0.056 | 2026-05-26 | data-quality · sprint | Phase 6 · Sprint 5 — Data quality (#24 + #26 + #28). #24 URL source audit: новый скрипт scripts/audit-urls.mjs (npm run audit:urls) извлекает URL из qna.js (поле src) и src/data/locations/*.json рекурсивно. Два regex: URL_HTTP_REGEX (с схемой) + URL_DOMAIN_REGEX (без схемы, для domain.tld/path). Делает HEAD-запрос (fallback на GET с Range при 405/501), параллельно (default 8 концурренции), с timeout 8 сек. Флаги: --concurrency N, --timeout MS, --format text|json|csv, --exit-on-broken, --limit N. Первый запуск: 256 уникальных URL найдено в проекте. #26 Source priority tier: новые helpers в app/primitives.jsx — classifySource(url) возвращает 'official'/'community'/'secondary' по whitelist доменов (gov.*, cba.am, ena.am, nalog.ru, pubmed.ncbi, freedomhouse.org, interpol.int — official; t.me, vc.ru, dtf.ru, habr.com, *.wiki, *.blog — community; всё остальное — secondary). Компонент <SourceTierBadge url /> с цветовыми тонами. Использован в app/screens/chat.jsx SourceSheetBody — каждая URL-ссылка теперь имеет badge доверия (офиц./втор./комм.). #28 Deprecation flow: <VerifyBadge> в app/screens/location.jsx дополнен опциональными props deprecatedSince (дата) и note (кастомный tooltip). При status === 'deprecated' показывает «⚠ устарело с YYYY-MM» вместо просто «устарело»; красный tone (var(--bad)) с opacity 0.85. Backwards-compatible — если deprecatedSince не передан, fallback к «устарело». Tooltip объясняет что данные потеряли актуальность. Все 4 места использования VerifyBadge в location.jsx обновлены прокидывать поля deprecatedSince и deprecationNote от данных. Прогресс Phase 6: 12 пунктов из 50 (#22, #23, #1, #36, #37, #39, #9, #10, #11, #24, #26, #28). |
| 0.055 | 2026-05-26 | ux · sprint | Phase 6 · Sprint 4 — UX-фундамент (#9 + #10 + #11). #11 ErrorBoundary (новый класс-компонент в app/primitives.jsx): React class ErrorBoundary с getDerivedStateFromError + componentDidCatch + reset(). Fallback UI с кнопками «попробовать снова» и «на главную», показывает сообщение ошибки (обрезано 240 символами), напоминание что данные в localStorage не потеряны. В app/shell.jsx PageResolver оборачивает screenContent в <ErrorBoundary key={boundaryKey} onReload={() => nav.reset(nav.tab)}> — ключ привязан к screen + stack.length или tab, чтобы при смене экрана пересоздавался boundary и сбрасывался error state. #10 Skeleton (новый компонент в primitives.jsx): CSS shimmer-placeholder через linear-gradient 200% backgroundSize + анимация rc-skeleton 1.4s (добавлена в app/styles.css). Props: width, height, lines (multi-line), gap, rounded. Использован в app/screens/location.jsx (3 строки скелета при deepLoading) и app/screens/compare.jsx CompareDeepSections (4 блока × 2 строки вместо «загрузка досье...»). #9 Empty states: <Empty> компонент дополнен пропом action (для CTA-кнопок). В app/screens/today.jsx добавлена CTA-карточка «Выберите целевой город» когда !targetCityId — без неё все секции про цель не показываются, новый пользователь оставался в неясности. Также conditions.jsx: rel="noreferrer" → "noopener noreferrer" (защита от tabnabbing, обнаружено при Sprint 3 review). Прогресс Phase 6: 9 пунктов из 50 (#22, #23, #1, #36, #37, #39, #9, #10, #11). |
| 0.054 | 2026-05-26 | security · sprint | Phase 6 · Sprint 3 — Security baseline (#36 + #37 + #39). Подготовка к публичному запуску. #36 CSP headers: в vercel.json добавлены Content-Security-Policy (с 'unsafe-eval' для Babel Standalone, 'unsafe-inline' для style-src из-за React inline-стилей; script-src ограничен unpkg.com; frame-ancestors 'none' для защиты от clickjacking; upgrade-insecure-requests), Strict-Transport-Security (1 год + includeSubDomains), X-Frame-Options DENY. #37 Sanitization: в app/primitives.jsx добавлены sanitizeUrl(url) (блокирует javascript:, data:, vbscript:, file:, blob:, filesystem: схемы; добавляет https:// к URL без схемы; whitelist для https?, mailto, tel) и sanitizeText(text, maxLength) (убирает управляющие символы, обрезает длину). app/screens/chat.jsx: saveEntry() теперь использует sanitizeText для q/a/src перед сохранением в localStorage; SourceSheetBody использует sanitizeUrl вместо ручного toUrl. app/screens/conditions.jsx: href={sanitizeUrl(e.link) ¦¦ '#'} для PubMed-ссылок + rel="noopener noreferrer" (защита от tabnabbing, было только noreferrer). Тестирование sanitizeUrl: 15 атакующих векторов (включая uppercase JAVASCRIPT:, http://javascript:alert(1), data:text/html) — все блокируются корректно. #39 Privacy disclosure: в app/screens/onboarding.jsx StepWelcome дополнен expandable Privacy-блоком: 🔒 «Все данные хранятся только в вашем браузере» с раскрывающимися деталями (что хранится локально, что НЕ уходит наружу, что грузится с CDN, права пользователя — export/delete). GDPR-friendly формулировки. Прогресс Phase 6: 6 пунктов из 50 завершены (#22 ✅, #23 ✅, #1 ✅, #36 ✅, #37 ✅, #39 ✅). |
| 0.053 | 2026-05-26 | data · loc | Ереван, Армения — полное досье 3-го города (Phase 6 #1). src/data/qna.js: +68 записей с loc: 'yerevan' по 15 тематическим блокам. Распределение: life=10, psy=8, med=7, gastro=6, visa=5, banks=5, capital=5, tax=5, home=5, tel=4, auto=4, pets=4. Каждый блок прошёл критический фактчек с привязкой источников (haywiki.org, ydoc.am, news.am, armgate.am, gov.am, freedomhouse.org и др.). Источники привязаны к каждой записи через поле src. src/data/locations/yerevan.json: 40 KB, 13 секций, 11 рисков, 11 pros / 10 cons. Расширения над схемой: legal.dtaStatus (СИДН РФ-РА 1996 ДЕЙСТВУЕТ полностью, НЕ приостановлен — уникально для Армении в нашем гайде), legal.extradition.icc (ратификация Римского статута МУС с 01.02.2024 — критический риск), infra.taxiAppsNote (Yandex Go работает в Армении, одна из 2 стран СНГ), pets.import.fromRussia.vsBulgaria (ЕАЭС-преимущество: 3-6 нед vs 5-6 мес для ЕС), community.itCompanies (PicsArt, Krisp, ServiceTitan), costs.vsKobuleti + costs.vsPomorie (прямые сравнения). src/data/locations/index.json: добавлена запись yerevan. Валидатор npm run validate:locations: 3/3 ✓ (39.7 KB). Поймал 5 нарушений схемы при первом запуске — все исправлены (visaThreshold: null → 0, coverage: "best in center" → "best", severity: "low-medium" → "medium" × 3). src/data/index.js: добавлена тонкая запись id: 'yerevan' в LOCATIONS с климатическими метриками (humidity 60, aqi 31, waterHard 2, earthquake 5, gastroScore 5, langBarrier 1, diaspora 5, tax 'ИП 5%'). Прогресс Phase 6: 3 пункта из 50 завершены (#22 ✅, #23 ✅, #1 ✅). |
| 0.052 | 2026-05-24 | tooling · audit | Phase 6 · #23 — Data freshness audit. Создан scripts/audit-freshness.mjs — авто-флаг устаревших записей. Сканирует QNA_SAMPLE в src/data/qna.js (поле updated) + top-level updated в src/data/locations/*.json. Универсальный парсер дат: YYYY-MM-DD, YYYY.MM, YYYY.MM.DD, MM.YYYY, MM-YYYY (qna.js имеет смешанные форматы). Группирует устаревшие по source-файлу, сортирует по «возрасту» (старшие сверху). CLI-флаги: --threshold N (дней, default 365), --format text|json|csv, --exit-on-stale (exit-code 1 для CI). npm-команда: npm run audit:freshness. Первый запуск: с порогом 365 — 0 устаревших (✓ все данные свежие после правок 2026.05); с порогом 60 — найдено 57 устаревших записей в qna.js (январь 2026 эпоха, ещё валидные но требуют ревизии). |
| 0.051 | 2026-05-23 | tooling · validate | Phase 6 · #22 — Schema CI validator. Создан package.json (private, type:module, devDeps: ajv@8 + ajv-formats + @babel/parser). Установлен node_modules локально (исключён через .gitignore). scripts/validate-locations.mjs — AJV Draft 2020-12 валидатор всех досье из манифеста с подробным error reporting (instancePath + ожидаемые значения + exit-code). scripts/validate-jsx.mjs — рекурсивный Babel-парсер .jsx/.js в app/, src/modules/, src/components/, src/data/. Команды: npm run validate:locations + npm run validate:jsx. Первый запуск поймал 13 настоящих нарушений в src/data/locations/pomorie.json: banking.recommendedBanks[0].tier: "primary" → "major", successRate: "medium-low" → "low", infra.transport.toHub.costLocal: object → number (3 EUR), 4× medicine.healthRisks[*].severity: "low-medium" → "medium", food.cuisine.spice: "low" → "mild", 3× community.schools.*.type (educational-center/private-school/embassy-school) → "private" с дополнительным полем subtype, 2× risks[*].severity: "low-medium" → "medium". После правок: оба досье валидны. JSX-валидатор: 37 файлов, 0 ошибок. |
| 0.050 | 2026-05-23 | docs · plan | Phase 6 · 50-step refinement plan добавлен в BACKLOG.md. 50 концентрированных пунктов из мульти-ролевого анализа (PM/UX/FE/BE/Data/T&S/Security/A11y/DevOps/Localization/BD), сгруппированы в 8 треков: данные/UX/инструменты/качество данных/performance/безопасность/engagement/дистрибуция. Каждый пункт с приоритетом (🔴 High 14 / 🟡 Medium 26 / 🟢 Low 10), сложностью (S/M/L), ведущей ролью и чекбоксом для прогресса. Карта критических зависимостей (#22 → #1–7 → #14 → #16; #36–39 → #47; #29 → #30 → #42; #48 → #1–7). Рекомендованная последовательность 12 спринтов до состояния «готов к публичному запуску» с 4–5 городами. Цель: единый источник правды для дальнейшей работы — что сделано, что нет, какие зависимости. |
| 0.049 | 2026-05-23 | feature · multi-city | Multi-city compare — task #27 из 50-step плана. app/screens/compare.jsx: cap COMPARE_MAX=3 городов. Хук useManifestIds() — список ID с углублённым досье через LocationLoader.list(). Фильтр shortlist к manifest-only (решение пользователя: «только города с dossier»). Реальный city-picker <CityPickerSheet> (замена «AI add hint») — Sheet с manifest-городами, не в шорт-листе. Inline remove (×) на каждой карточке (виден когда >2 городов). Новая секция <CompareDeepSections> lazy-load досье через useLocationDeep (3 хука всегда, slice по locs.length — Rules of Hooks safe), 5 вкладок: жильё / виза-налоги / банки / медицина / риски. Хелперы: DeepRent (1br/2br/коммуналка с поддержкой summaryLocal для EUR + summary USD), DeepLegal (visaFree + permitCount с audience-filter + СИДН + extradition), DeepBanking (банки + карты + SEPA + крипта), DeepMedicine (level + hub + страховка + audience-specific keyPoint), DeepRisks (critical/high count + топ-1 по audience с ?? 4 для critical=0). app/screens/today.jsx: новая карточка 04.4. сравнение после TargetCityBrief — видна когда в шорт-листе ≥2 dossier-городов, прямой путь Today → Compare минуя Atlas. app/screens/location.jsx: toggle shortlist уже был — 3-cap enforce'ится в Compare на уровне отображения. Babel-parse: оба файла валидны. |
| 0.048 | 2026-05-23 | data · loc | Поморие, Болгария — полное досье города (2-я локация после Кобулети). src/data/qna.js: +75 записей с loc: 'pomorie' по 15 тематическим блокам (климат/виза/банки/капитал/налоги/недвижимость/медицина/еда/интернет/транспорт/животные/сообщество/психология/бюджет/риски). Распределение: life=11, med=8, psy=8, visa=7, banks=7, tax=6, capital=6, home=5, gastro=5, tel=4, auto=4, pets=4. Каждый блок прошёл критический фактчек — отфильтровано >30 неточностей источников (устаревшие санкционные списки, несуществующие банки «Tokyga»/«Phantastico», ошибочные ставки ЕТ 10% vs реальные 15%, неверная цифра НЗОК-взноса €15-20 vs реальные €38, ложная информация о Раффайзенбанке Болгарии, путаница Беломорье vs Болгарское Поморие в Блоке 1 и др.). src/data/locations/pomorie.json: 55 KB, 13 секций, 11 рисков, 8 pros / 9 cons. Расширения над схемой Кобулети с сохранением структуры: параллельные поля *Local для EUR-валюты (Болгария в еврозоне с 01.01.2026), legal.vat + socialContributions + dtaStatus + extradition.toUS (прецедент 26.03.2026), banking.sepa + banking.crypto (MiCA), medicine.atopyTreatment с дупилумабом через НЗОК и частно, pets.import.fromRussia с RNATT + 3 мес ожидания + минимальный возраст 7+ мес, costs.startupCostLocal (расходы первых 3 мес), vsKobuleti для прямого сравнения. src/data/locations/index.json: добавлена запись pomorie. |
| 0.047 | 2026-05-22 | data · cleanup | Чистка pre-2026 source attributions — удалены устаревшие год-маркеры из source-полей. qna.js: 75 строк src очищены автоматически по паттернам 2024-2025 / 2025-2026 / 2025–26 / практика 2025 / условия 2025 (стейл атрибуции). Сохранены 16 легитимных научных/исторических ссылок — ACG IBS Guidelines 2023, Gut Journal 2022, EASD руководство 2024, WHO Water Quality 2022, OutRush research 2025, meduza.io 2024, Минфин письмо 2023, Investment Guide 2025 — это конкретные публикации с версией. index.js: 5 комментариев обновлены (AQI IQAir 2024 → IQAir последние данные, рынок подрос за 2024-25 → актуальная цена 2026). extras.js: 3 заголовка обновлены (Numbeo 2025-Q4 → Numbeo актуальная корзина, IQAir 2024-25 → IQAir последние данные). UI: basket.jsx и location.jsx — sub-тексты «Numbeo 2025-Q4» обновлены. BACKLOG.md, FILES.md: source-аттрибуции освежены. Сохранены без изменений: CHANGELOG (исторический журнал), Design Brief v3.0 (versioned doc), legacy/, имена законов/договоров (Минская 1993, Закон 2017, ИТ-Закон 2023), исторический контекст («с 2022 банки РФ отключены», «с 2022 диаспора 40k+»). Парсинг qna.js — валиден, 164 entries, 87 loc:kobuleti. |
| 0.031 | 2026-05-20 | feature · migrate | ScreenQnA — app/screens/qna.jsx. Mobile-first «База знаний»: горизонтальный scroll-pill категорий + вкладка «для меня ★» (персонализация: audienceTags + targetCity + health.conditions), поиск по 105+ вопросам, Q&A аккордеон с badge категории, FAB-кнопка AI → Sheet (textarea + ответ + сохранение в localStorage), redirect в ScreenChat при недоступности API. Stub ScreenQnA удалён. |
| 0.030 | 2026-05-20 | feature · migrate | ScreenWellbeing — app/screens/wellbeing.jsx. Mobile-first «Психологическая опора»: 3 таба (фазы/отметка/история). Фазы: 6 swipe-карточек с симптомами, инструментами, экстренным + ЖКТ-протоколами (фазы 3–5). Отметка: 6 dot-шкал (цветные кнопки) + textarea + «Рефлексия» (5 универсальных полей вместо «дневника пары»). ЖКТ-дневник: все видят + profile.health.conditions highlight. История: mini SVG-линии (7 дней, 6 метрик) + expand-карточки записей. Stub ScreenWellbeing удалён. |
| 0.029 | 2026-05-20 | feature · migrate | ScreenCompare — app/screens/compare.jsx. Mobile-first «Сравнение локаций»: hero с лидером и дельтами, горизонтальный scroll-snap с CityCard (score + burn/save + tap-to-edit 11 параметров), профиль по критериям с mini-bars, WeightsSheet (слайдеры → сохранение в profile.weights), DevilsAdvocate для текущего лидера, GastroSheet-справочник, CTA «Добавить город через AI» → ScreenChat. Stub ScreenCompare удалён. |
| 0.027 | 2026-05-20 | feature · migrate | ScreenConditions — app/screens/conditions.jsx. Mobile-first «Болезни и климат»: сегментед-фильтр «мои / все», карточки диагнозов с inline top/bottom-3 через score bars, detail-Sheet с 3 sub-tabs (рейтинг / факторы / источники). Интеграция с profile.health.conditions (фильтр диагнозов пользователя). Кнопка перехода в профиль. Entry point из app/screens/location.jsx TabHealth → «атлас здоровья». Удалён stub ScreenConditions. |
- 0.0XX — pre-release итерации, любые правки.
- Каждая логически отдельная единица работы получает следующий порядковый номер.
- Минорная версия
0.1будет выставлена при первом полном smoke-test в браузере + ручной верификации курса/виз/PMID. - Версия
1.0— после первого деплоя в production с подтверждёнными данными (не плейсхолдерами).
| Тип | Что |
|---|---|
docs |
Документация — *.md файлы, комментарии, описания |
data |
Слой данных — src/data/*.js, новые поля у локаций |
styles |
CSS — src/styles/*.css |
feature |
Новая страница/модуль |
fix |
Исправление бага |
integration |
Подключение новых файлов в index.html / роутинг в app.jsx |
audit |
Проверочный проход без правок кода |
refactor |
Перенос/переименование без изменения поведения |
Перед следующей логической правкой:
- Открыть этот файл.
- Добавить строку в таблицу с инкрементированным номером (
0.016,0.017, …). - Указать дату, тип, краткое описание (≤120 символов), ссылку на основной затронутый файл.
- Если правка крупная — добавить развёрнутое описание в CHANGELOG.md.