Image приходит с pre-cached APKINDEX (старые package versions с момента
build'а image). После repo swap (sed на internal mirror) apk всё ещё
использовал stale cache — просил ca-certificates-20251003 когда в mirror
только 20260413 → HTTP 404.
Fix: explicit `apk update` после sed, перед `apk add`. Это refresh'ит
APKINDEX с нашего mirror; apk теперь видит actual available versions.
Применено к 3 jobs (commit-msg-lint, build-docs prep step внутри release,
propose-prod-image-bump). После этого release должен пройти на следующем
main pipeline → semantic-release минтит v2.31.4 → propose-prod-image-bump
auto-создаст infra MR без manual intervention.
QA report BUG-005: "/search" возвращал 0 результатов на любые запросы
(«антенна», «GS_MOSCOW», «super» — всё пусто).
Root cause: SearchController convertedil scope enum в lowercase перед
WHERE filter:
data_scope = ANY(CAST(? AS TEXT[])) -- value: 'public'
Но DB stores UPPERCASE per migrations 0008/0009 CHECK constraint:
CHECK (data_scope IN ('PUBLIC', 'INTERNAL', 'RESTRICTED'))
Filter 'public' никогда не матчил 'PUBLIC' → empty result для всех queries.
Багу 5+ месяцев — comment в коде утверждал что DB lowercase, что было
неправдой даже в момент написания.
Fix: убрать .toLowerCase() — pass enum names как есть. Verified:
curl /api/v1/antenna/records?as_scope=PUBLIC возвращает GS_MOSCOW_A1
но curl /api/v1/search?q=GS_MOSCOW возвращал 0. После fix должен матчить.
Runner не имеет outbound к dl-cdn.alpinelinux.org (Permission denied):
```
WARNING: fetching https://dl-cdn.alpinelinux.org/alpine/v3.20/main: Permission denied
ERROR: unable to select packages: curl, git, jq (no such package)
```
Это сломало propose-prod-image-bump в v2.31.3 tag pipeline (failed) — пришлось
вручную создавать infra MR. Тот же риск висел над commit-msg-lint и build-docs.
Fix: sed-замена /etc/apk/repositories на `repo.nstart.cloud/repository/alpine`
перед каждым apk add. Probe подтвердил что mirror отвечает 200 на
`/repository/alpine/v3.20/main/x86_64/APKINDEX.tar.gz`.
Touches: 3 Alpine-based jobs (commit-msg-lint, build-docs, propose-prod-image-bump).
Form review после !224 показал что create mode имеет тот же баг что и edit:
SchemaDrivenForm без переданного defaultValues fallback'ит на '' для
validFrom, и пользователь видит пустое поле «Действует с».
Добавлен defaultValues={{ validFrom: nowIsoLocal() }} для create — consistent
UX с edit mode (!224 fix). Other formы (drafts, reviews, schema workflow) —
либо read-only либо без validFrom, clean.
User feedback: «крайняя дата должна быть исходя из реальной в этом
словаре». Раньше slider всегда extended до now + 30d даже без причины —
если нет scheduled записей на 18.05, slider всё равно тянулся туда,
пользователь блуждал по пустоте.
Теперь max = max(marks) — last real change point (record validFrom включая
upcoming scheduled, которые backend возвращает в /scheduled-summary).
Если все marks в прошлом — max=now (always reachable). Если есть future
scheduled на 18.05 — slider тянется ровно туда, не дальше.
Touches: TimeTravelPicker.tsx (inline editor) + TimeTravelModal.tsx (modal).
Три связанных UX бага после v2.31.0 release:
1. **TimeTravel marks**: после !220 slider extended до now+30d, но отметок в
future window не было → blind drag. Backend теперь возвращает
upcomingValidFroms (top 50) в /scheduled-summary; frontend merges как marks.
2. **Non-empty banner**: empty-state hint работает только когда список пуст.
Если currently active записи есть AND ещё запланированы — пользователь
не видит. Добавлен info banner над таблицей с тем же текстом + CTA.
3. **Editor "Действует с"**: edit mode оставлял поле пустым (bitemporal
policy «новая версия с now»). User feedback: «показывает ничего, а даты
заданы» — выглядит как баг. Prefill nowIsoLocal() — теперь юзер видит
конкретную дату как точку отсчёта.
MR !220 расширил range у TimeTravelPicker.tsx, но юзер видит другой компонент —
TimeTravelModal.tsx (fullscreen modal с компарисоном now vs picked moment).
У него своя range logic с max = now + pad (1 day), и фикс !220 туда не дотянулся.
User feedback: создал запись с validFrom=18.05, открыл time-travel modal — слайдер
14.05 → 16.05, не дотянуть до 18-го.
Apply same 30-day future window logic к TimeTravelModal так чтобы оба компонента
вели себя одинаково.
Fixes UX bug: пользователь создаёт запись с validFrom на будущую дату
(e.g. 18.05), открывает catalog 15.05 — пустая таблица, никаких хинтов
о том что запись существует.
Backend (read-api):
- New endpoint GET /api/v1/{dict}/records/scheduled-summary
- Returns {count, nearestValidFrom} для записей с validFrom > now AND
validTo > now в текущем scope view
- Single-aggregate JPA query (COUNT + MIN), respect scope filter same way
as listActive — INTERNAL scheduled не учитывается для PUBLIC consumer'а
Frontend:
- New useScheduledSummary hook + ScheduledRecordsSummary type
- Empty-state в dictionaries.$name now shows: "Запланировано N, ближайшая
на DD.MM [Перейти к этой дате]" с CTA который set'ает time-travel на
nearestValidFrom
- i18n keys для ru/en plural forms
Связан с MR !219 (TimeTravelPicker 30-day future window) — теперь slider
позволяет дойти до даты записи, и empty-state hint позволяет узнать что
запись существует.