// Единый источник версии веб-приложения. Инкрементируется при каждой
// существенной правке (semver). Отображается в ProfileMenu (десктоп) и на
// вкладке «Аккаунт» (мобильное), синхронизируется с APK versionName в
// mobile/android/app/build.gradle.
//
// Когда менять:
//   PATCH — багфикс, мелкий UI-фикс.
//   MINOR — новая фича без breaking changes.
//   MAJOR — несовместимое API/schema изменение.
//
// 0.6.3 (2026-06-09) — аудит-фиксы tech-debt (deploy/mobile/config).
// 0.7.0 (2026-06-15) — спринт: списание склада, маршруты цеха, папки сборки,
//   каскад удаления заявки, auth-стабильность + логирование, вход 1-тап,
//   резерв всей спеки, разбивка резерва, роутинг-фиксы. ИНКРЕМЕНТ КАЖДЫЙ ДЕПЛОЙ.
// 0.7.1 (2026-06-16) — синхронизация: FG-маршрут из плана+редактора (рассеиватель
//   ТПА→ВАК→Склад№2 больше не схлопывается в простой), ВАК=металлизация, checkBOM
//   читает реальный остаток Склада№2 (Барашек G), pfSku на партиях запуска для
//   списания со склада, гард запуска без спеки, диагностика списания (alLog).
// 0.7.2 (2026-06-16) — резерв W2 при создании заявки: резерв считается по BOM
//   ПРОДУКТА заявки (не хардкод Гранты), reserveFullSpec (W1+W2) вместо W1-only,
//   reserveForAssembly резолвит ПФ по имени (apadog/клапан без invId теперь
//   резервируются). Цех не запускает производство того, что есть на Складе №2.
// 0.7.3 (2026-06-16) — мультиагентный раунд 8 фиксов Цеха/резерва + adversarial
//   review: дубль партии после переноса (split только для локальных + cuid-
//   реконсиляция), приход ПФ на Склад№2 (writeoff резолв по bomN/имени, не падает
//   на pfSku=null), сборка ФГ достигает complete при ненулевом W2-стоке, Гранта G
//   спека не затирается пустым снапшотом, резерв на готовый ПФ а не сырьё,
//   завершение заявки приходует ФГ на Склад№3, правка остатка W2 → StockMovement,
//   Outsource-квадрат + редактируемое кол-во отправки в карточке.
// 0.7.4 (2026-06-16) — ground-truth раунд (по live-снапшоту): каскад остатков
//   W2 (снапшот пере-захватывал _seedStock из уже посчитанного stock → +delta
//   каждый снапшот) — фикс: _seedStock переносится при rebuild INVENTORY_FULL +
//   фиксируется в редакторе до optimistic-записи; партии Цеха с requestId=null
//   (самостоятельные запуски) больше НЕ прячутся орфан-фильтром (возврат/исчезание
//   позиций); превью резерва (RequestDetailsModal + calcRequest) — stockAware и по
//   BOM продукта, а не хардкод Гранты.
// 0.7.5 (2026-06-16) — (1) ФИКС регресса v0.7.4: орфан-фильтр Цеха снова прячет
//   партии с requestId=null (createRequest-сбои/сироты, НЕ легит-запуски — легит
//   запуск создаёт заявку и линкует партии). Возврат «удалил заявку → 3 позиции в
//   Цеху». (2) Совместный резерв: calcBomReserve показывает ВСЕ 3 группы вместе —
//   готовые ПФ (Склад№2, в наличии) + сырьё на производство дефицита (Склад№1) +
//   фурнитура (Склад№1). _w2-строки только для отображения (резерв W2 — assembly).
//   Проверено на реальном BOM С-161: 21 строка (4 ПФ + 2 сырьё + 15 фурнитура).
// 0.7.6 (2026-06-16) — (1) РЕЗЕРВ единым каналом: ПФ Склада№2 + сырьё W1 + фурнитура
//   W1 резервируются ВСЕ через per-invId /reserve (персистит). reserveForAssembly
//   убран из reserveFullSpec — он слал W2 в /assembly-reserve, не сохранявший
//   кастом-спеки (invId) → «ПФ не резервировались» (assemblyReserves=0 на сервере).
//   (2) ПРАВА не слетают: sticky-роль (держим роль AUTH.user-владельца при
//   downgrade whoami), refresh по COOKIE без in-memory accessToken (401/heartbeat/
//   mount). Durable: backend ACCESS_TOKEN_TTL=15m → нужен bump (env, на LAN-HTTP
//   Secure-cookie рефреша не работает).
// 0.7.7 (2026-06-17) — 3 корневых фикса (глубокий аудит): (1) учёт склада —
//   pfSku партий = row.invId (не синтетика BOM-N) → приход ПФ виден на складе;
//   (2) права на заводском LAN-HTTP — cookie secure/sameSite из env + ожила
//   sticky-роль (roleKey→level); (3) партии-сироты — только с requestId,
//   реконсиляция по _linkKey, дедуп-гард. + материалы AL и пр. в редакторах.
// 0.8.0 (2026-06-17) — СПРИНТ «полный цикл наряда» (Н1–Н5, мультисессионно):
//   частичная отгрузка готовых фар на Склад №3 (shipFinishedToW3 — приход ФГ +
//   расход ПФ/покупных пропорц. отгрузке + drawdown резерва + стикеры паллет
//   A4/QR + сканер); наряд показывает Заявлено·Отгружено·Осталось; досрочное
//   закрытие с ОБЯЗАТЕЛЬНОЙ причиной (брак/веская) → AuditLog; корректируемый
//   ПФ-batch (overproduction покомпонентно) на 3-м окне запуска; drag «Буфер/
//   Склад№2 → Сборка» в Цеху. Backend: Shipment-модель + миграция
//   20260617120000_shipment + POST /shipment + POST /request/:id/close + shipments[]
//   в снапшоте. Reconcile: учёт ФГ/ПФ ТОЛЬКО на отгрузке (confirmAssemblyPart —
//   чистый прогресс); фикс 3 reload-течей резерва (release/releaseForRequest/
//   releaseAssembly всегда бьют в backend, идемпотентно). Верифицировано: backend
//   на реальном Postgres + fullcycle 40/40 + reserve-edge 10/10 + vitest 182/182.
// 0.8.1 (2026-06-17) — фиксы по тестам отгрузки частями (Александр): (1) DROP
//   готового ПФ НАПРЯМУЮ в 3 конечные зоны — «Сборка»/«Буфер производства»/
//   «Склад №2», без обязательной цепочки через буфер; (2) папка сборки держится
//   по наряду (shipped>0) — БОЛЬШЕ НЕ пропадает после частичной отгрузки/отката
//   партий (раньше зависела от наличия партии в asm). Подтверждено: резерв
//   кратно отгрузке, «минус отгружено» в наряде, ФГ на Складе №3 — работают.
//   ⏭ Баг «перенос партии в Цеху не персистится → возврат на ТПА» (cechTransfers
//   не доходят до backend, optimistic-расхождение) — отдельный аккуратный фикс.
// 0.8.2 (2026-06-17) — фиксы по тестам Цеха/отгрузки (Александр): (1) БАГ «возврат
//   на ТПА» — createTransfer теперь вызывается ВСЕГДА (резолв backend-cuid партии
//   даже для локальной, через _batchByRequestId) → переносы партий в Цеху
//   персистятся на backend (раньше createTransfer=0 в логах, только commit→404);
//   (2) drop готового ПФ в 3 конечные зоны напрямую (Сборка/Буфер/Склад№2); (3)
//   папка сборки ИСЧЕЗАЕТ при закрытии наряда (status done/cancelled); (4) синий
//   бейдж «отгружено N» на готовой фаре Склада №3 (до закрытия наряда). Резерв на
//   отгрузке = v0.8.0 (W2+покупные; сырьё W1 — на производстве через writeoff):
//   «резерв не меняется на отгрузке» лечится фиксом переносов п.1 (Цех→W2 теперь
//   персистится → writeoff W1 срабатывает). Harness 41/41 + reserve-edge 10/10.
// 0.8.3 (2026-06-17) — усиление фикса Цеха (launch_flow уведомляет Цех о
//   реконсиляции local→cuid, чтобы перенос точно персистился) + changelog
//   версии при наведении в меню аккаунта.
// 0.8.4 (2026-06-17) — changelog в меню читаемый + раскрывается ВНИЗ; синий бейдж
//   отгруженного теперь и в плиточном виде Склада №3 (раньше только в списке);
//   диагностика переноса партий в Цеху (alLog('cech')) для отладки «возврата на ТПА».
// 0.8.5 (2026-06-17) — ОКОНЧАТЕЛЬНЫЙ фикс «возврата на ТПА» (доказан на live-данных
//   ORDER-MQIQHNAG: createTransfer=0, 4 партии застряли в origin). 3 корня:
//   (A) performTransfer резолвил backend-cuid по УСТАРЕВШЕМУ closure-снимку items
//   (тайл уже перерисован backend-партией, а closure держал локальную) → cuid не
//   находился → createTransfer не звался; теперь резолв по АВТОРИТЕТНОМУ item из
//   prev.items (как newShipmentId). (B) merge оставлял ЛОКАЛЬНЫЙ дубль партии
//   рядом с backend-тайлом (реконсиляция local→cuid терялась: cech mirror-эффект
//   осиротил ссылку launch_flow, FG-PF-путь не диспатчил) → пользователь тащил
//   дубль; теперь дубль с backend-близнецом (по requestId-маппингу externalId→cuid
//   + pfSku/имя) ДРОПАЕТСЯ, split-партии сохраняются. (C) резолвер матчит по pfSku
//   (стабильнее имени). Доказано харнессом 11/11 на РЕАЛЬНЫХ формах с live-БД.
// 0.8.6 (2026-06-18) — НАСТОЯЩИЙ корень «возврата на ТПА» (доказан probe'ом на
//   живой вкладке: перенос делался, но createTransfer НИ РАЗУ не вызывался, без
//   ошибок). performTransfer ставил newShipmentId/realQty ВНУТРИ setState-
//   updater'а и читал ПОСЛЕ setState; React НЕ гонит updater синхронно при
//   непустой очереди обновлений (Цех под SSE — постоянно) → значения null →
//   весь блок отправки на backend ПРОПУСКАЛСЯ. Теперь расчёт СИНХРОННЫЙ (stateRef
//   + монотонный shipSeqRef), вызов createTransfer БЕЗУСЛОВНЫЙ → перенос
//   персистит (backend двигает stages на create), тайл не откатывается. commit/
//   cancel читают _serverId из stateRef. + очередь: 4xx (404 commit/несущ. id) —
//   ДРОПается, не зацикливается (был флуд 404). + диагностика cech.transfer.try/resp.
// 0.8.7 (2026-06-18) — синий бейдж «↑N» (отгружено по открытому наряду) на
//   готовой фаре Склада №3 ТЕПЕРЬ КАСКАДИТСЯ на папки/группы как amber-резерв:
//   shippedFgForSku/shippedItemsInGroup/shippedSumInGroup (data_materials);
//   shipBadge на плитках подгрупп (WarehouseStripe), подподгрупп и inline-позиций
//   (warehouse_pages). Гаснет при закрытии наряда. Источник — getRequestProgress.
// 0.8.8 (2026-06-18) — ФИКС «Гранта не резервируется» (детерминированный, по
//   live-логам сервера: 78× FK-400 `InventoryReserve_requestId_fkey`). КОРЕНЬ:
//   резерв писался с локальным/внешним ключом заявки (ORDER-XXX = externalId), а
//   FK ссылается на Request.id (cuid) → ORDER-XXX ≠ cuid → FK-400, резерв не
//   писался. Плюс очередь повторяла 4xx ВЕЧНО (флуд 400 от мёртвой ORDER-MQIR77ZL).
//   ФИКС в reserve/release/assembly-методах: (A) резолв requestId→cuid по REQUESTS
//   (id|externalId) перед POST; (B) 4xx → drop из очереди, не зацикливать
//   (зеркало фикса очереди Цеха 0.8.6). Рабочий путь С-161 не затронут.
// 0.8.9 (2026-06-18) — КНОПКА ОБНОВЛЕНИЯ без ручных манипуляций (Александр:
//   убрать Ctrl+Shift+R / Ctrl+F5 / F12). Приложение само опрашивает version.jsx
//   (no-store, каждые 2 мин + на фокус); при новой версии на сервере — зелёный
//   бейдж на иконке аккаунта + кнопка «Обновить» в выпадашке версии. Клик →
//   window.__applyUpdate(): чистит ВСЕ кэши + обновляет SW + перезагрузка →
//   network-first тянет свежий фронт. Данные на сервере не теряются.
// 0.9.0 (2026-06-18) — РЕЗЕРВ «мигает и исчезает» — ФИКС. Корень: reserve-POST
//   падал транзитно (403 при резолве роли viewer→admin, ИЛИ FK-400 гонка с
//   коммитом createRequest), а мой 4xx-drop из 0.8.8 ДРОПал его навсегда →
//   оптимистичный резерв затирался снапшотом (0 на сервере). Теперь дроп ТОЛЬКО
//   при безнадёжном отказе (404 / FK на удалённую заявку); 403 и FK-на-живую-
//   заявку — РЕТРАЯТСЯ (_permanentReserveFail). Резерв доезжает и держится.
// 0.9.1 (2026-06-18) — ФИКС «алюминий литьевой не резервируется». Корень:
//   RAW_MATERIAL_SPECS содержал только 6 пластиков, AL не было → позиции
//   сырья INV-RAW-AL не существовало, и строка резерва AL (для C-161 и любой
//   фары с AL-литьём) уходила в никуда. Добавлен AL в RAW_MATERIAL_SPECS
//   (INV-RAW-AL, кг, АК12) + MATERIAL_WASTE['AL']. Резерв алюминия теперь
//   записывается, как у пластиков. Снапшот сохраняет позицию (preserve unknown).
// 0.9.2 (2026-06-22) — ФИКС резерва лампы (invId). Корень (sweep на дампе боевой
//   БД): код передавал ЧИСЛОВОЙ id номенклатуры лампы (1303) вместо складского
//   invId (INV-PUR-ELE-016). Для спек СО строкой-лампой (Гранта L/R, GRN-FL) →
//   POST /api/state/reserve падал 400 на числовом invId → резерв НЕ создавался
//   ВЕСЬ; для спек без неё (C-161 и др.) → reserveLampForRequest(_findInv→null)
//   тихо не резервировал лампу. Добавлен _resolveLampInvId (по sku→имени→фолбэк)
//   в launch_flow (effectiveBom + оба reserveLampForRequest + _lampInvId).
//   Верифицировано: 21/21 FG-спек OK (резерв вкл. AL+лампа, отгрузка кратно).
// 0.9.3 (2026-06-22) — РЕКУРСИВНАЯ РАЗУЗЛОВКА ПОДСБОРОК (корпус G/GFL). Корпус в
//   спеке фары — ЕДИНАЯ позиция «под сборку»: при резерве заявки есть готовый
//   корпус на Складе №2 → берётся ЦЕЛИКОМ (его внутренности барашек/шестерни/
//   винты повторно НЕ резервируются — они внутри); нет → разузловывается
//   РЕКУРСИВНО (тело-PP в сырьё W1 + барашек/шестерни снова «есть на W2 целиком /
//   нет → сырьё» + винты только на производимые корпуса); частично — N целиком +
//   остаток из сырья. calcBomReserve переписан с плоского прохода на обход дерева
//   с отрезанием поддерева на ПФ-узле + дедуп дублей flatten (раньше корпус и его
//   дети считались плоско-независимо → двойной резерв). Цех симметрично: при
//   корпусе-в-наличии барашек/шестерни в Цех НЕ запускаются (был рассинхрон).
//   Резолв ПФ сторона-aware (L/R) — подстраховка (в живой спеке invId уже точен).
//   Верифицировано на ЖИВОЙ спеке GRN-R (3 сценария) + 8 рекурсивных vitest +
//   190/190 backend + babel-parse.
// 0.9.4 (2026-06-22) — консолидированный релиз (несколько сессий): (1) Цех —
//   подтверждение детали в сборке синхронизируется со статусом в зоне (матч
//   детали↔партии по pfSku↔invId, переживает SSE-снапшот; раньше _bomN терялся
//   после снапшота → статус не синхронизировался); (2) Цех зона «Склад №3» —
//   история частичных отгрузок наряда (N ед · дата); (3) экспорт/импорт базы
//   знаний через Excel в Настройках; (4) раздел «Обновления» в Настройках (вся
//   история версий). + фикс данных на сервере: шайба д10 C-161 = изготавливаемая
//   (internal/PVC/«ТПА→Склад №2»).
// 0.9.5 (2026-06-22) — НОРМАЛИЗАЦИЯ СПЕК: спека фары ссылается на под-спеки ПФ
//   (корпус/бленда/отражатель…) + покупное + лампа, СЫРЬЯ в FG-спеке НЕТ. Корпус
//   — подсборка из ПФ (тело-PP + ссылки на барашек/шестерни/бабочку + метизы).
//   Резерв (рекурсия 0.9.3 по sku-ссылкам, правок не требует): ПФ есть на W2 →
//   целиком, нет → разузловка вглубь. Цех (launch_flow `_walkCech`): недостающие
//   ПФ на ВСЕХ уровнях — отдельными позициями (корпус → барашек/шестерни, если их
//   тоже нет). Списание ОДНОУРОВНЕВОЕ (`_consumeOneLevel`+writeoffPfBatch): партия
//   ПФ потребляет ПРЯМЫЕ входы (тело→сырьё W1, внутренности→ПФ со Склада №2,
//   покупные→W1), НЕ рекурсивно до сырья → двойного расхода нет. shipFinishedToW3
//   уже одноуровневое. Данные: 62 под-спеки + 4 FG записаны в Postgres через
//   PUT /api/taxonomy/specification. 26 vitest + live-снапшот.
// 0.9.6 (2026-06-22) — УЧЁТ СКЛАДА в Excel + раскол плитки дашборда. (1) Новый
//   лист «Учёт склада» в экспорте: текущий остаток (из движений склада) vs
//   неснижаемый остаток, статус «требуется закуп» (покупное W1) / «требуется
//   изготовление» (ПФ W2/W3) при остатке ≤ минимум+10%. Остаток и минимум
//   РЕДАКТИРУЕМЫ прямо в листе — импорт применяет: количество → движение склада
//   (adjust на разницу), минимум → карточка позиции (minStock). (2) Импорт из
//   Excel применяет ВСЕ редактируемые поля (не только sku/name); перенос группы
//   по имени, недостающие группы создаются. (3) Главная: плитка «Требуется закуп»
//   разделена на «Требуется закуп» (покупное) и «Требуется изготовление» (ПФ).
//   master_data_io.jsx + dashboard.jsx. Verified Playwright; задеплоено scp.
// 0.9.7 (2026-06-22) — СТОРОНА-МОДЕЛЬ L/R + ЛАМПЫ ПО СЛОТАМ + имена ПФ + rename.
//   (1) Сторона-модель: общие (generic) полуфабрикаты (барашек/бленда/бабочка/
//   шестерни/очки/крышки/опоры/тешка/заглушки/ручка/упор/пыльник) сведены в ОДНУ
//   каноничную позицию (-L); сторона-дубль (-R) удалён (23 спеки + 23 позиции;
//   сток дублей ~0, барашек 10000 — дубль, канон держит свои 10000). Сторона-
//   специфичны ТОЛЬКО корпус/рассеиватель/отражатель (KRP/RAS/REF/RDH/RPV). Спеки
//   фар и корпусов ссылаются на каноничные generic-ПФ → GRN-L и GRN-R по общим
//   деталям ИДЕНТИЧНЫ, различаются корпус/рассеив/отраж. (2) Выбор лампы ПО СЛОТАМ
//   (строки-лампы спеки; Гранта 3: H4/S25/T20), дефолт = лампа спеки; детектор
//   бульба по ПЕРВОМУ слову (держатели и «Пыльник/Прижим …лампы» — НЕ лампы; фикс
//   JS-бага \b после кириллицы). Шаг «Выберите лампу» НЕ пропускается (спека без
//   ламп → ручной слот, дефолт «Без лампы»). Списание ламп по слотам, спец-лампы
//   гейтятся отгрузкой (нет дубля с shipFinishedToW3). Только FG, к ПФ не относится.
//   (3) Имя ПФ-спеки резолвится со складской позиции по sku (был артикул
//   WIP-BAR-GRN-L вместо «Барашек G»). [Пункт (4) «Лампочка»→«Лампа» УБРАН из changelog
//   2026-06-22: по факту НЕ применён — 3 позиции (BOM-016/017/018) и 6 узлов спек
//   остались «Лампочка». Соседи задекларировали, но не сделали.] Живая база:
//   PUT спек (репойнт+rename ПФ) + DELETE дублей -R. Верифицировано:
//   calcBomReserve на живом стоке (GRN-L==GRN-R generic), 10/10 слотов, 8/8 vitest.
// 0.9.8 (2026-06-22) — ЗАКОН «ОСТАТОК ≥ 0» (требование Александра). Остаток позиции
//   физически не может быть < 0 — клампится Math.max(0,…) в ЦЕНТРАЛЬНОЙ точке
//   (data_server_sync: эффективный остаток = посев + Σ движений), все экраны читают
//   it.stock → видят ≥0, и Excel «Учёт склада» (показ + импорт количества floor'ятся).
//   Ниже нуля уйти нельзя: из нуля ничего не изготовить. Backend-reject движения <0
//   (авторитетный уровень) — отдельным заходом. Файлы: data_server_sync.jsx,
//   screens/master_data_io.jsx. Verified Playwright (netDelta -99999 → 0, импорт -50 → 0).
// 0.9.9 (2026-06-22) — АЛЮМИНИЙ (литьевой/магнетрон) + ЗАКОН ≥0 на backend.
//   (1) calcBomReserve/writeoff: литьевой AL → реальная позиция INV-PUR-MISC-603
//   (не призрак INV-RAW-AL; AL убран из RAW_MATERIAL_SPECS — больше нет 13-й
//   плитки-фантома в «Пластиках»); магнетрон-расходник INV-PUR-MISC-605 на узлах
//   с «Металлизация»/«ВАК» в маршруте, 7 г/деталь. Общий резолвер _rawInvIdFor
//   в data_materials + лукапах dashboard/mobile/mobile_dashboard (резерв и показ
//   не разъезжаются). Плоские спеки (C-161/Веста/Калина/Солярис/Приора/РС-100)
//   считают магнетрон; нормализованные ПФ Гранты — после простановки маршрута.
//   (2) backend mutations.ts (REBUILD): /api/state/stock-movement клампит дельту
//   так, чтобы нетто-остаток не ушёл ниже нуля (+ AuditLog) — авторитетный
//   уровень закона ≥0 (фронт-кламп 0.9.8 — лишь показ; иначе negative-debt на
//   сервере поглотил бы будущий приход). Verified: vitest 190/190, equivalence
//   frontend↔backend цел, live-снимок (C-161: 603=13.8кг + 605=200 деталей).
//   (3) ЛАМПЫ — папки 12В/24В/LED/накаливания на СЕРВЕРЕ. Серверные позиции
//   ламп не хранят voltClass → «Склад»→«Лампы» был плоским, и выбор лампы при
//   запуске фильтровал по voltClass → группы пустые. Фикс: при гидрации снимка
//   (data_server_sync) для w1-lamps деривируем voltClass/bulbType из имени
//   (_deriveLampMeta, экспортирован из data_nomenclature). 15 ламп → 7/2/2/4,
//   0 «Прочее». Локальная раскладка теперь и на сервере.
// 0.9.10 (2026-06-22) — ФИКС ВОСКРЕШЕНИЯ ДУБЛЕЙ ЛАМП (backend REBUILD). Позиции
//   BOM-016/017/018 «Лампочка H4/S25/T20» задваивали реальные «Лампа»
//   (INV-PUR-ELE-016/017/018) на «Складе». Корень: `seed-taxonomy.ts` запускается
//   на КАЖДЫЙ старт контейнера (Dockerfile CMD) и пересоздавал их
//   (BOM_GROUP_MAP 16/17/18→w1-lamps) → `deploy.ps1 --force-recreate` (0.9.9)
//   воскресил ранее удалённые дубли. Фикс: 16/17/18 убраны из BOM_GROUP_MAP
//   (дубли не создаём), rename «Лампочка»→«Лампа», grantaTree лампы → реальные
//   INV-PUR-ELE-* (LAMP_INVID) + самолечащий deleteMany (item+движения+резервы)
//   на каждом старте сида. tsc чист.
// 0.9.11 (2026-06-22) — ЛАМПЫ: официальные имена + рамка дефолта; РЕЗИНА → покупное.
//   (1) При запуске фары подпись слота, имя лампы по умолчанию, имя наряда и Цех
//   теперь берут ОФИЦИАЛЬНОЕ каталожное имя по invId (_catalogLampForRow в
//   launch_flow.jsx), а не легаси-имя узла спеки → больше нет рассинхрона «слот
//   S25 12V Amber» vs «карточка PY21W». Рамка выбранной лампы — нормализованное
//   сравнение invId (INV-PUR-ELE-016 == PUR-ELE-016) → дефолт подсвечивается.
//   Живые спеки GRN-L/R-H4 n16/17/18 приведены к каталогу (PUT, 6 узлов).
//   (2) Резина (Пароотвод/Прокладка/Бондаж/Втулка/Кольцо) перенесена ПФ(W2)→
//   покупное сырьё(W1) в новую папку «Уплотнители - покупное» (w1-upl); папка W2
//   → «Уплотнители - ПФ». seed-taxonomy.ts: +w1-upl в GROUPS, BOM_GROUP_MAP
//   25/26→w1-upl, имена ламп BOM_GRANTA_L → каталожные (re-seed-safe). backend REBUILD.
// v0.9.12 (2026-06-23): корпус-подсборка → ОТДЕЛЬНЫЙ наряд при дефиците (буфер +
//   своя папка сборки в Цеху + приход готового корпуса на Склад №2, фара забирает
//   с W2; резерв на входы корпуса, без фантома); «Тело корпуса G/GFL» — отдельный
//   ПФ; задел ПФ на окне «Наличие» разделён на «Спецификация фары»/«Спецификация
//   корпуса»; папка наряда в зоне «Сборка» появляется СРАЗУ при запуске; при
//   дефиците корпуса — 2 папки (фара+корпус); честный индикатор комплектности;
//   кнопка удаления спеки ПФ; убран тумблер плитки/список в Спецификациях; дубли
//   «Корпус G/GFL (подсборка)» удалены. (Часть данных — тело-ПФ/дедуп/репойнт — на
//   живом сервере применяется отдельным data-push, см. deploy/QUEUE.md.)
// v0.9.15 (2026-06-24): Цех — наряд-корпус в окне сборки шлёт «на Склад №2» (была
//   ошибочная «на Склад №3»). Склад №3 / главная — «ПФ как запчасть» (корпус +
//   рассеиватель) встроены плитками-в-рамке в полосу Склада №3 рядом с готовыми
//   фарами, дублируют подгруппы Склада №2 «под реализацию»; пустая подгруппа-дубль
//   «Рассеиватели» (SPG) убрана; на Складе №2 у корпуса/рассеивателя бейдж «ПФ-запч.».
//   Архив нарядов: завершённый наряд «Сохранить в архив» вместо удаления + кнопка
//   «Архив» на полосе буфера и на странице «Сборщик»; архив переживает перезагрузку.
//   «Сборщик» показывает реальные наряды в производстве (была пустая страница).
//   Главная «Готовые продукты» — реальный остаток Склада №3, разбит по типам
//   (ПТФ / Фара-блок-фара / Фонарь / ДХО). Кнопки шапки 📌/✎ опущены под шапку.
//   backend REBUILD: новая колонка Request.archived (миграция) + seed убирает SPG.
// v0.9.16 (2026-06-25): БАТЧ нескольких сессий.
//   (1) Кнопка «Корректировка» на позиции склада (1/2/3) ожила → открывает полную
//   модалку: готовая фара / составной ПФ → редактор спецификации (дерево BOM, как в
//   Настройках, правится прямо со склада, автосохранение); простая деталь/сырьё →
//   развесовка (вес ед./масса остатка/материал/поставщик/резерв) + правка остатка
//   (проводкой движения склада). Развесовка в шапке редактора подтягивает веса из
//   спек полуфабрикатов рекурсивно (ПФ-ссылка → её спека → … до листа).
//   (2) Бейдж «вес примерный» (фиолетовый) на позициях/подгруппах/группах склада +
//   развесовка 2 ДХО Hyundai по аналогу.
//   (3) Печать QR-стикеров паллет: номер наряда больше не вылазит за лист;
//   постраничное превью (по 4, клик→зум); 2 режима — «По стандарту» (равное кол-во)
//   и «Выборочно» (своё кол-во на каждой паллете, печать по очереди).
//   (4) Главная: группы готовой продукции (ПТФ/Фара/Фонарь/ДХО) и «ПФ как запчасть»
//   стали выпадающими (свёрнуто по умолчанию); у ПФ добавлена полоса наличия.
//   (5) Надёжность: React/Babel теперь локальные (работа без интернета на заводе);
//   авто-переподключение синхронизации при разрыве/возврате вкладки; честные
//   баннеры «раздел в разработке» в Настройках (Сотрудники/Роли) + кнопка
//   «Установить приложение» (PWA) видна прямо в шапке.
//   (6) backend REBUILD (F072): защита от двойного учёта движения склада —
//   PARTIAL UNIQUE INDEX по КОМПОЗИТУ (sourceId, invId) (одна операция = много
//   движений по invId, но повтор той же пары — no-op P2002). Пред-чек прода:
//   0 дублей пар (sourceId,invId) → миграция применилась чисто. (Дубли по
//   ОДНОМУ sourceId — 48 групп — легит: разные invId одной отгрузки.)
// v0.9.17 (2026-06-25): ПЕРЕНОС ПОЗИЦИИ ПО ИЕРАРХИИ СКЛАДА. На каждой плитке
//   позиции — кнопка ⇄ (правый верхний угол, под ролью менеджер). Открывает
//   дерево всей иерархии (Склад → подгруппа, подподгруппы — контекстом),
//   выбор папки-назначения → «Подтвердить» → позиция переезжает ФИЗИЧЕСКИ
//   (API.updateItem → PATCH /api/taxonomy/item, персист + SSE всем терминалам)
//   + оптимистичное обновление. Ссылки на позицию идут по артикулу (sku/invId)
//   — при переносе не ломаются; производственный маршрут не трогаем (это план
//   цеха, не место хранения). Фронт-only, backend уже на 0.9.16.
// v0.9.18 (2026-06-25): PWA-кнопка «Установить» — ВСЕГДА видна и в ЛЮБОМ браузере
//   + на ЭКРАНЕ ВХОДА (раньше была только в шапке после логина и пряталась на
//   Firefox → Александр в Comet её не видел). type=button (не сабмитит форму).
//   Клик: есть beforeinstallprompt → нативная установка в окно; нет → понятная
//   инструкция по браузеру; на HTTP подсказывает открыть https-адрес
//   (155.212.161.65.sslip.io), т.к. установка возможна только из secure-контекста.
// v0.9.19 (2026-06-25): PWA-УСТАНОВКА ЗАРАБОТАЛА — НАСТОЯЩИЙ корень. Манифест
//   ссылался на /icons/icon-192.png и icon-512.png (+maskable), а на сервере в
//   icons/ лежали ТОЛЬКО .svg — PNG отсутствовали (деплой-тары их не включали,
//   были только в репо). 404 на иконки → Chrome НЕ считал приложение
//   устанавливаемым → beforeinstallprompt НЕ слался даже в Chrome по https →
//   кнопка показывала лишь подсказку. Фикс: выложены icon-192/512 + maskable +
//   apple-touch на FirstVDS (200, image/png, верные размеры). Теперь критерии
//   PWA выполнены → нативная установка в Chrome/Edge по https-адресу.
//   ⚠ Деплой-тар фронта ДОЛЖЕН включать icons/ + manifest.webmanifest.
// v0.9.20 (2026-06-25): PWA-кнопка КОРРЕКТНА ВО ВСЕХ БРАУЗЕРАХ. Улучшен детект
//   платформы (Safari macOS/iOS + iPad-iOS13 по maxTouchPoints + Opera/Yandex)
//   и подсказки: Chrome/Edge/Opera/Яндекс → нативная установка (значок в
//   адресной строке); Firefox десктоп → «не поддерживает, откройте в Chromium»;
//   Safari Mac → «Файл → Добавить в Dock»; Safari iPhone/iPad → «Поделиться → На
//   экран Домой». Кнопка ПРИСУТСТВУЕТ в каждом браузере (UA-тест 7/7).
// v0.9.21 (2026-06-25): ЗАПУСК ПФ НА ПРОИЗВОДСТВО — РЕЗЕРВ СЫРЬЯ, А НЕ ГОТОВОГО
//   ПФ. Баг (Александр): запуск партии полуфабриката (напр. Тешка) на
//   изготовление резервировал уже готовый такой же ПФ со Склада №2 вместо сырья,
//   из которого он делается — если ПФ был в наличии, сырьё не резервировалось
//   вовсе. Общий для ВСЕХ полуфабрикатов. Корень: reserveForRequest жёстко звал
//   calcBomReserve со stockAware:true (правильно для СБОРКИ фары — берём готовые
//   ПФ; неверно для ПРОИЗВОДСТВА самого ПФ). Фикс: stockAware теперь
//   override-able (дефолт true), PF-путь launch_flow передаёт stockAware:false →
//   разузловка в сырьё (лист-ПФ → его материал/масса; подсборка-корпус →
//   рекурсивно тело + дети + закупные). +3 теста (196/196 backend PASS).
// v0.9.22 (2026-06-25): ДОРОЖНЫЕ КАРТЫ ПФ + ЛАК ПРИ ПРОИЗВОДСТВЕ + РАСХОД СЫРЬЯ.
//   (1) 44 полуфабриката Гранты/GFL/С-161 не имели цеховой дорожной карты — их
//   нельзя было провести по цеху/запустить в производство. Проставлены маршруты:
//   простые→«ТПА→Склад№2»; рассеиватели→«ТПА→Лакировка→Склад№2»; отражатели→
//   «…→Металлизация→Склад№2» (пластик) / «Литьё AL→Мех→Голтовка→Лак→Металл→
//   Склад№2» (алюминий); корпус-в-сборе→«Сборка→Склад№2»; бленда→«ТПА→Склад№2»;
//   очки напыленные→металлизация. Починены «Закупка» у рассеивателей Гранты и
//   оборванный маршрут рассеивателя Соляриса-R. (2) ЛАК (защитный/базовый)
//   списывается при ИЗГОТОВЛЕНИИ полуфабриката (этап лакировки), а не при сборке
//   фары — и ровно ОДИН раз (раньше лак-child спеки не списывался при
//   производстве; на сборке фары лак/магнетрон больше не списываются повторно).
//   (3) Расход сырья PFS и ТЭП (TPV) починен — для них не было позиции сырья на
//   складе, расход обнулялся; позиции созданы. (4) Убраны мёртвые синонимы «ВАК»
//   из словаря маршрутов (канон — «Металлизация»). Данные (маршруты/лак/сырьё) —
//   data-push на сервер; код — data_cech.jsx + data_materials.jsx.
// v0.9.23 (2026-06-25): БЕЙДЖ «ВЕС ЛАКА ПРИМЕРНЫЙ». Лак (защитный/базовый),
//   добавленный в спеки полуфабрикатов, имеет placeholder-массу 10 г (0.01 кг) —
//   реальный расход лака на деталь нужно выверить. В редакторе спецификации
//   (Настройки → Спецификации) на строке-лаке теперь фиолетовый бейдж «вес
//   примерный — требует корректировки» (тот же, что на складе для развесовки по
//   аналогу). Помечается флагом weightApprox на строке спеки. (settings.jsx)
// v0.9.24 (2026-06-25): ЭКСПОРТ/ИМПОРТ EXCEL — СПЕЦИФИКАЦИИ ГОТОВОЙ ПРОДУКЦИИ.
//   Лист «03_Спецификации» в выгрузке стал РЕДАКТИРУЕМЫМ и заливается обратно:
//   состав каждого изделия (№, наименование, кол-во, ед., тип, invId, вес, материал,
//   страна, маршрут, фото) — готовая продукция сверху, полуфабрикаты ниже, перед
//   каждым изделием строка-шапка. Фото — список имён + кликабельная ссылка на
//   изображение. Импорт (Настройки → Бэкапы → Импорт из Excel) пересобирает дерево
//   по ключу SKU+Путь, шлёт на сервер ТОЛЬКО изменённые изделия (PUT specification),
//   показывает их в предпросмотре. Защита состава: дубль «Путь» в изделии или
//   удаление узла-родителя с оставшимися детьми → изделие ОТКЛОНЯЕТСЯ с
//   предупреждением; числа с пробелом-разделителем не схлопываются молча в 1.
//   Скрытые _node_json/_photos_json + лист _base_specs хранят оригинал. (master_data_io.jsx)
window.APP_VERSION = '0.9.24';

// Даты выпуска версий (для раздела «Обновления» в Настройках). Формат YYYY-MM-DD.
window.APP_VERSION_DATES = {
  '0.9.24': '2026-06-25',
  '0.9.23': '2026-06-25',
  '0.9.22': '2026-06-25',
  '0.9.21': '2026-06-25',
  '0.9.20': '2026-06-25',
  '0.9.19': '2026-06-25',
  '0.9.18': '2026-06-25',
  '0.9.17': '2026-06-25',
  '0.9.16': '2026-06-25',
  '0.9.15': '2026-06-24',
  '0.9.14': '2026-06-24',
  '0.9.13': '2026-06-24',
  '0.9.12': '2026-06-23',
  '0.9.11': '2026-06-22',
  '0.9.10': '2026-06-22',
  '0.9.9': '2026-06-22',
  '0.9.8': '2026-06-22',
  '0.9.7': '2026-06-22',
  '0.9.6': '2026-06-22',
  '0.9.5': '2026-06-22',
  '0.9.4': '2026-06-22',
  '0.9.3': '2026-06-22', '0.9.2': '2026-06-22', '0.9.1': '2026-06-18',
  '0.9.0': '2026-06-18', '0.8.9': '2026-06-18', '0.8.8': '2026-06-18',
  '0.8.7': '2026-06-18', '0.8.6': '2026-06-18', '0.8.5': '2026-06-17',
  '0.8.4': '2026-06-17', '0.8.3': '2026-06-17', '0.8.2': '2026-06-17',
  '0.8.1': '2026-06-17', '0.8.0': '2026-06-17', '0.7.7': '2026-06-17',
  '0.7.6': '2026-06-16', '0.7.5': '2026-06-16', '0.7.4': '2026-06-16',
  '0.7.3': '2026-06-16', '0.7.2': '2026-06-16', '0.7.1': '2026-06-16',
  '0.7.0': '2026-06-15', '0.6.3': '2026-06-09',
};

// Краткий changelog для меню аккаунта (наведение на номер версии) и раздела
// «Обновления» в Настройках. Короткие, понятные строки — что изменилось для
// пользователя. Версия → массив строк-пунктов. Мини-попап показывает только
// текущую версию; Настройки → всю историю (сортировка по semver вниз).
window.APP_CHANGELOG = {
  '0.9.24': [
    'Экспорт в Excel теперь включает спецификации готовой продукции: на листе «Спецификации» — состав каждого изделия (детали, количество, единицы, материал, маршрут, фото). Готовая продукция сверху, полуфабрикаты ниже, перед каждым изделием — строка с его названием.',
    'Эту таблицу можно править прямо в Excel и загрузить обратно (Настройки → Бэкапы → Импорт из Excel): система примет изменения состава изделий — показывает их в предпросмотре перед применением.',
    'Фото детали — кликабельной ссылкой (открывает изображение); чтобы отвязать фото, убери его строку в ячейке. Новые фото добавляются в карточке спецификации в приложении.',
    'Защита от ошибок: если в одном изделии задублирован «Путь» или удалён узел-родитель с оставшимися деталями — такое изделие не зальётся, появится понятное предупреждение (состав не испортится).',
  ],
  '0.9.23': [
    'Лак (защитный и базовый), добавленный в спецификации полуфабрикатов, считается в килограммах с примерной массой 10 г на деталь — её нужно выверить под реальный расход. В редакторе спецификации (Настройки → Спецификации) на строке лака теперь фиолетовый бейдж «вес примерный — требует корректировки» (такой же, как на складе для веса по аналогу).',
  ],
  '0.9.22': [
    'Дорожные карты цеха проставлены полуфабрикатам Гранты, Гранты-FL и С-161, у которых их раньше не было, — теперь эти детали (рассеиватели, отражатели, бленды, шестерни, корпуса и др.) можно запускать в производство и проводить по цеху. Корпус в сборе идёт через зону «Сборка» на Склад №2. Исправлены рассеиватели Гранты (ошибочно помеченные «Закупка») и рассеиватель Соляриса, у которого маршрут не доходил до склада.',
    'Лак (защитный и базовый) теперь списывается при изготовлении самого полуфабриката — на этапе лакировки — и ровно один раз. Раньше лак не списывался при производстве полуфабриката, а при сборке фары мог списаться повторно. Магнетрон — аналогично. Герметик по-прежнему списывается при сборке фары.',
    'Исправлен расход сырья для деталей из материалов PFS и термоэластопласта (ТЭП): раньше расход не списывался со склада, так как для этих материалов не было заведено позиции сырья. Позиции созданы.',
  ],
  '0.9.21': [
    'Запуск полуфабриката на производство теперь резервирует сырьё, из которого он изготавливается, а не списывает уже готовый такой же полуфабрикат со Склада №2. Раньше при запуске партии (например, Тешки) на изготовление система бронировала готовый полуфабрикат, если он был в наличии, — и сырьё под производство не резервировалось. Касалось всех полуфабрикатов. Для сборки готовой фары поведение прежнее (готовые полуфабрикаты по-прежнему берутся со склада).',
  ],
  '0.9.20': [
    'Кнопка «Установить» теперь корректно работает во всех браузерах. Chrome, Edge, Opera, Яндекс.Браузер — устанавливают приложение в отдельное окно. Firefox на ПК установку не поддерживает — кнопка подсказывает открыть в Chrome/Edge. Safari на Mac — «Файл → Добавить в Dock»; на iPhone/iPad — «Поделиться → На экран „Домой“». В каждом браузере кнопка на месте с правильной инструкцией.',
  ],
  '0.9.19': [
    'Установка приложения (PWA) теперь действительно работает. Причина, по которой раньше не предлагалась установка: на сервере не хватало иконок приложения (192 и 512 пикселей), без которых браузер не считает сайт приложением. Иконки добавлены. Теперь в Chrome или Edge по адресу https://155.212.161.65.sslip.io кнопка «Установить» открывает приложение в отдельном окне.',
  ],
  '0.9.18': [
    'Кнопка «Установить» теперь видна в любом браузере и сразу на экране входа (раньше пряталась до входа и в части браузеров). Где браузер поддерживает установку (Chrome/Edge и др. на https) — устанавливает приложение в отдельное окно; где нет — показывает понятную инструкцию. Если открыли по http — подсказывает открыть защищённый адрес https://155.212.161.65.sslip.io, иначе браузер установку не разрешает.',
  ],
  '0.9.17': [
    'Перенос позиции между складами и папками. На каждой плитке позиции появилась кнопка ⇄ в правом верхнем углу (под ролью «менеджер»). Нажатие открывает дерево всей иерархии склада (Склад → подгруппа, подподгруппы показаны для ориентира), выбираете папку-назначение и жмёте «Подтвердить» — позиция переезжает не только на экране, но и физически: сохраняется на сервере и обновляется на всех терминалах.',
  ],
  '0.9.16': [
    'Кнопка «Корректировка» на позиции склада (1/2/3) теперь работает. Для готовой фары или составного полуфабриката открывается спецификация — её можно править прямо со склада (добавить/изменить/удалить компоненты, веса, количество; сохраняется автоматически). Для простой детали/сырья — карточка с развесовкой (вес единицы, масса остатка, материал, поставщик, резерв) и корректировкой остатка.',
    'Развесовка изделия в спецификации считается с учётом весов всех полуфабрикатов — вес подтягивается из спецификации каждого узла рекурсивно, до самой детали (раньше у изделия из ссылок-полуфабрикатов общий вес выходил нулевым).',
    'На складе у позиций, подгрупп и групп с примерным («выдуманным») весом — фиолетовый бейдж, чтобы сразу видеть, что вес требует уточнения. Добавлены примерные веса корпуса и рассеивателя Hyundai.',
    'Печать QR-стикеров паллет: номер наряда больше не вылазит за лист; постраничный просмотр стикеров (по 4, клик — крупно); два режима — «По стандарту» (равное количество на каждой паллете) и «Выборочно» (своё количество на каждой паллете, печать по очереди).',
    'Главная: группы готовой продукции (ПТФ / Фара / Фонарь / ДХО) и «ПФ как запчасть» сворачиваются (клик по заголовку раскрывает); у полуфабрикатов появилась полоса наличия.',
    'Кнопка «Установить приложение» теперь видна прямо в шапке (раньше пряталась, если браузер не предлагал установку).',
    'Надёжность: приложение работает без интернета на заводе (библиотеки загружаются локально, не из сети); синхронизация сама переподключается при обрыве связи или возврате на вкладку; в Настройках честно помечены разделы «в разработке».',
    'Защита от двойного учёта на складе: при плохой связи повторная отправка одной и той же операции больше не может списать или начислить остаток дважды (на уровне базы данных).',
  ],
  '0.9.15': [
    'Цех: при производстве корпуса отдельным нарядом в окне сборки кнопка теперь правильно «Принять корпус на Склад №2» (раньше при открытии папки писалось «Отгрузить на Склад №3»). Полуфабрикаты едут на Склад №2, готовые фары — на Склад №3.',
    'Склад №3 и главная: корпус и рассеиватель «как запчасть» показаны рядом с готовыми фарами на Складе №3 (в зелёной рамке, дублируют подгруппы Склада №2 — то же название, код и количество, под реализацию). На Складе №2 у корпуса и рассеивателя — отметка «ПФ-запч.». Пустая подгруппа-дубль «Рассеиватели» со Склада №3 убрана.',
    'Архив нарядов: завершённый наряд теперь можно «Сохранить в архив» вместо удаления. Кнопка «Архив» — на полосе буфера заявок и на странице «Сборщик»; открывает список архивных нарядов с возможностью восстановить. Архив переживает перезагрузку страницы.',
    'Страница «Сборщик» теперь показывает реальные наряды в производстве (раньше была пустой). Туда же ведёт плитка «В производстве» с главной.',
    'Главная, блок «Готовые продукты»: показывает реальный остаток Склада №3, разбитый по типам изделий — ПТФ, Фара / блок-фара, Фонарь, ДХО.',
    'Кнопки «Скрыть шапку» (📌) и «Редактировать склад» (✎) опущены под шапку; при скрытой шапке поднимаются в верхние углы.',
  ],
  '0.9.14': [
    'Расходные материалы считаются в своих единицах. Герметик — в миллилитрах (был ошибочно в штуках). На шаге «Проверка комплектности» у каждой позиции теперь видна единица: «× 5 мл», «нехв. 5000 мл», «× 0.01 кг» — а не голое число, которое читалось как штуки.',
    'Лак (защитный и базовый) и магнетрон добавлены отдельными редактируемыми строками во все полуфабрикаты, где идёт лакировка/металлизация. Количество задаётся в кг и правится прямо в спецификации (раньше лак висел заглушкой «1 кг» и не редактировался). Магнетрон считается один раз (убран двойной счёт).',
    'Корпуса блок-фар больше не металлизируются и не лакируются (C-161, С-018 Калина, ДХО Солярис, С-018 2170) — идут с литья сразу на склад/сборку; лишний расход магнетрона на корпус убран.',
    'Удалены неиспользуемые позиции «Корпус 003 (Hyundai)» и «Рассеиватель 003 (Hyundai)».',
    'Редактор спецификации: в выборе единицы добавлены «мл» и «г» (раньше герметик показывался как «шт»).',
  ],
  '0.9.13': [
    'Экран входа: вместо выбора пользователя карточкой — обычная форма «Логин и пароль». Логин — это email (например, aleksandr@autosvet.local), пароль вводится вручную и больше не хранится в коде приложения.',
    'Вход в систему теперь один: убрано отдельное окно браузера с логином/паролем при открытии сайта — сразу открывается форма входа.',
  ],
  '0.9.12': [
    'Производство корпуса отдельным нарядом. При запуске фары, если готового корпуса нет на Складе №2, автоматически создаётся отдельная заявка на производство корпуса — она видна в буфере заявок и получает свою папку в зоне «Сборка» Цеха. Готовый корпус приходит на Склад №2, откуда фара забирает его при сборке.',
    'Цех: папка наряда в зоне «Сборка» появляется сразу после запуска партии (раньше — только после того, как в зону занесли первую деталь). При дефиците корпуса в «Сборке» видны две папки — фара и корпус.',
    'Честный индикатор комплектности на шаге подтверждения запуска: «✓ Комплектность» / «⚙ Корпус в производство» / «⚠ Дефицит» (раньше всегда показывалось «✓ Комплектность»).',
    'Резерв при дефиците корпуса: на несуществующий корпус резерв больше не вешается (нет «зарезервировано при нулевом остатке»). Резервируются входы корпуса — сырьё тела и комплектующие — под нарядом корпуса.',
    '«Тело корпуса G» и «Тело корпуса GFL» теперь отдельные полуфабрикаты со своим складским учётом и партией; корпус ссылается на них.',
    'Задел (выпуск сверх наряда) на окне «Наличие» разделён на две группы — «Спецификация фары» и «Спецификация корпуса»: тело корпуса, барашек и шестерни можно лить с запасом отдельно от остальной фары.',
    'Спецификации: добавлена кнопка удаления спецификации полуфабриката (корзинка в строке, как у готовых фар). Убрана кнопка-переключатель «плитки/список» — всегда список. Убраны дубли «Корпус G/GFL (подсборка)» — остались только канонические «Корпус фары G» / «Корпус в сборе GFL».',
  ],
  '0.9.11': [
    'Выбор лампы при запуске: подпись слота и название лампы теперь показывают официальное складское название (например, «Лампа PY21W 12V 21W (BAU15s)»), а не устаревшее «Лампа S25 12V Amber» — слот и карточка больше не расходятся. Лампа по умолчанию отмечена рамкой.',
    'Резиновые уплотнители (Пароотвод, Прокладка, Бондаж, Втулка, Уплотнительное кольцо) перенесены из полуфабрикатов (Склад №2) в покупное сырьё (Склад №1) — новая папка «Уплотнители - покупное». Папка на Складе №2 переименована в «Уплотнители - ПФ».',
  ],
  '0.9.10': [
    'Убраны дубли ламп: «Лампочка H4 / S25 / T20» (BOM-016/017/018) задваивали настоящие «Лампа …» на «Складе». Они возвращались при каждом перезапуске сервера из фонового наполнения базы — теперь оно их не создаёт и само удаляет. На складе остаётся один полный набор ламп «Лампа …».',
  ],
  '0.9.9': [
    'Алюминий теперь учитывается двумя реальными позициями из группы «Алюминий»: литьевой (тело литых деталей — расход по массе) и для магнетрона (расходник металлизации — 7 г на каждую металлизируемую деталь, включая пластиковые корпуса и отражатели). Призрак «Алюминий 5000 кг» в «Пластиках» убран — резерв и расход теперь идут на настоящие складские позиции.',
    'Остаток на складе не может уйти в минус и на сервере: движение, которое увело бы остаток ниже нуля, ограничивается нулём (из нуля ничего не изготовить). Это закрепляет закон «остаток ≥ 0» на уровне базы, а не только на экране.',
    'Лампы на «Складе» снова разложены по папкам 12 В · 24 В · LED · Лампы накаливания (как было локально) — раньше на сервере они показывались одним списком. Выбор лампы при запуске тоже снова сгруппирован по этим папкам.',
  ],
  '0.9.8': [
    'Остаток на складе больше не может быть отрицательным: число всегда ноль или больше. Ниже нуля уйти физически нельзя — из нуля ничего не изготовить. Закреплено в расчётах, на всех экранах и в Excel «Учёт склада» (и при показе остатка, и при импорте количества).',
  ],
  '0.9.7': [
    'Выбор лампы при запуске — теперь по гнёздам: у Гранты три лампы (H4, S25, T20) выбираются по отдельности, по умолчанию подставлены лампы из спецификации. Ламподержатели и детали вроде «пыльник/прижим центральной лампы» больше не считаются лампами и не подменяются.',
    'Шаг «Выберите лампу» показывается всегда; для изделий без лампы в спецификации можно вручную добавить лампу или оставить «Без лампы».',
    'Общие полуфабрикаты (барашек, бленда, бабочка, шестерни, очки, крышки, опоры и т.д.) больше не двоятся на левую и правую сторону — сведены в одну позицию; левая и правая фара берут их с одного склада. По сторонам различаются только корпус, рассеиватель и отражатель.',
    'Полуфабрикаты в Настройках → Спецификации снова показываются по названию (например, «Барашек G»), а не артикулом.',
  ],
  '0.9.6': [
    'Новый лист «Учёт склада» при экспорте в Excel: по каждой позиции виден текущий остаток и неснижаемый остаток, и статус «требуется закуп» (покупное) или «требуется изготовление» (полуфабрикаты) — загорается, когда остаток подходит к минимуму (минимум + 10%).',
    'Количество и неснижаемый остаток можно править прямо в листе «Учёт склада» — после импорта файла они применяются: количество меняется движением по складу, минимум записывается в карточку позиции.',
    'Импорт из Excel теперь применяет правки всех полей (артикул, наименование, группа, материал, поставщик и т.д.), а не только артикула и названия. Перенос позиции в другую группу — по имени группы; недостающие группы создаются автоматически.',
    'На главной плитка «Требуется закуп» разделена на две: «Требуется закуп» (покупное) и «Требуется изготовление» (полуфабрикаты).',
  ],
  '0.9.5': [
    'Спецификации фар переведены на ссылки: фара состоит из готовых полуфабрикатов (корпус, бленда, отражатель…), а сырьё указано в спецификации каждого полуфабриката отдельно. При создании заявки и в сборке виден чистый список полуфабрикатов — без сырья.',
    'Корпус — отдельная подсборка из своих полуфабрикатов (барашек, шестерни, бабочка). Если корпуса не хватает на складе, в Цех попадают только реально недостающие детали, а списание идёт без двойного расхода материала.',
  ],
  '0.9.4': [
    'Цех: подтверждение детали в сборке теперь синхронизируется со статусом в зоне (раньше после обновления страницы статус «поступило» мог не отображаться).',
    'Цех, зона «Склад №3»: показывается история отгрузок наряда — сколько единиц и когда отгружено, по каждой частичной отгрузке.',
    'Настройки: экспорт и импорт базы знаний (номенклатура и спецификации) через Excel.',
    'Настройки: новый раздел «Обновления» — полная история версий с описанием изменений.',
  ],
  '0.9.3': [
    'Корпус фары теперь учитывается как единая позиция «под сборку»: если готовый корпус есть на Складе №2 — он берётся целиком, без повторного резерва его деталей (барашек, шестерни, винты). Если корпуса нет — резервируется сырьё на его изготовление (рекурсивно, до самых деталей); если корпуса хватает частично — часть берётся целиком, остаток собирается из сырья.',
    'Цех больше не запускает партии деталей корпуса (барашек, шестерни), когда сам корпус берётся со склада целиком.',
  ],
  '0.9.2': [
    'Выбранная лампа теперь действительно резервируется при запуске для всех изделий. Исправлена ошибка с неверным складским кодом лампы: из-за неё для блок-фар Гранта запуск вообще не создавал резерв, а для остальных фар лампа не попадала в резерв.',
  ],
  '0.9.1': [
    'Литьевой алюминий теперь резервируется при запуске (C-161 и другие фары с AL-литьём) — раньше для него не было позиции сырья на складе.',
  ],
  '0.9.0': [
    'Резерв при запуске больше не «мигает и исчезает» — он переотправляется при кратком сбое прав или гонке и надёжно сохраняется.',
    'Кнопка «Обновить» прямо в приложении: при новой версии — зелёный бейдж на иконке аккаунта, нажал «Обновить» — и всё обновилось. Больше никаких Ctrl+Shift+R и DevTools.',
  ],
  '0.8.9': [
    'Появилась кнопка «Обновить» прямо в приложении и зелёная точка на иконке аккаунта при выходе новой версии — больше не нужны Ctrl+Shift+R и DevTools',
  ],
  '0.8.8': [
    'Резерв при запуске теперь сохраняется для всех изделий (Гранта и др.) — раньше резерв молча не записывался из-за рассинхрона ключа заявки',
    'Убран бесконечный поток ошибок 400 от «зависшей» операции резерва удалённого наряда',
  ],
  '0.8.7': [
    'Синий бейдж «отгружено N» на готовой фаре Склада №3 теперь виден и на папках/группах (как amber-резерв) — на самой позиции и на всех папках над ней',
  ],
  '0.8.6': [
    'Цех: перенос партии ТЕПЕРЬ РЕАЛЬНО сохраняется на сервере — позиция больше не откатывается на ТПА/литьё (исправлен глубокий баг: отправка на сервер молча пропускалась)',
    'Убран бесконечный поток ошибок 404 от застрявших повторов в очереди',
  ],
  '0.8.5': [
    'Цех: перенос партии по дорожной карте — позиция больше не возвращается на ТПА после перезагрузки',
    'Убран двойник партии (локальная копия рядом с серверной) после запуска наряда',
  ],
  '0.8.4': [
    'Окно «что нового» в меню — читаемое и раскрывается вниз',
    'Синий бейдж «отгружено» теперь виден и в плиточном виде Склада №3',
  ],
  '0.8.3': [
    'Цех: переносы партий по дорожной карте надёжно сохраняются — позиция больше не возвращается на ТПА',
    'Меню аккаунта: список правок версии при наведении на номер',
  ],
  '0.8.2': [
    'Цех: готовый ПФ можно дропнуть сразу в «Сборку» / «Буфер» / «Склад №2»',
    'Папка сборки исчезает после завершения наряда',
    'Синий бейдж «отгружено N» на готовой фаре Склада №3 (до закрытия наряда)',
  ],
  '0.8.1': [
    'Папка сборки больше не пропадает после частичной отгрузки',
  ],
  '0.8.0': [
    'Частичная отгрузка готовых фар на Склад №3 (по частям)',
    'Наряд показывает «Заявлено · Отгружено · Осталось»',
    'Стикеры паллет A4 с QR-кодом + сканер на телефоне',
    'Досрочное закрытие наряда с обязательной причиной',
    'Правка тиража ПФ на запуске (можно налить больше про запас)',
  ],
  '0.7.7': [
    'Произведённые полуфабрикаты теперь корректно приходуются на склад (раньше приход мог «теряться»)',
    'Права доступа больше не слетают на заводском сервере по локальной сети (HTTP)',
    'Убраны «партии-сироты» в Цеху, не привязанные ни к одному наряду',
  ],
  '0.7.6': [
    'Резерв полуфабрикатов Склада №2 теперь надёжно сохраняется',
    'Права доступа перестали слетать — роль владельца держится при кратких сбоях связи',
  ],
  '0.7.5': [
    'После удаления наряда в Цеху больше не остаются «висящие» позиции',
    'Окно резерва показывает сразу три группы: готовые ПФ на складе + сырьё на изготовление + покупная фурнитура',
  ],
  '0.7.4': [
    'Исправлен накапливающийся «раздув» остатков Склада №2',
    'Позиции Цеха от самостоятельных запусков больше не исчезают и не «возвращаются» назад',
    'Превью резерва считается по спецификации самого изделия, а не всегда по Гранте',
  ],
  '0.7.3': [
    'Пакет из 8 исправлений Цеха и резерва: убран дубль партии после переноса, корректный приход ПФ на склад, сборка фары доходит до готовности при наличии деталей на складе',
  ],
  '0.7.2': [
    'Резерв при создании заявки считается по спецификации заказанного изделия',
    'Цех больше не запускает в производство то, что уже есть на Складе №2',
  ],
  '0.7.1': [
    'Синхронизация склад↔цех: маршрут детали берётся из плана и редактора маршрутов (этап металлизации / ВАК больше не теряется)',
    'Проверка комплектности читает реальный остаток Склада №2',
  ],
  '0.7.0': [
    'Большой спринт: списание склада при производстве, редактор маршрутов Цеха, папки сборки',
    'Каскадное удаление наряда, вход в одно касание, резерв всей спецификации, стабильность прав доступа',
  ],
  '0.6.3': [
    'Технические доработки развёртывания, мобильной сборки и конфигурации',
  ],
};
