fix: validate saved plan confidence

This commit is contained in:
ik
2026-09-21 18:23:19 +07:00
parent b0421c11f5
commit f3eb54083f
3 changed files with 10 additions and 2 deletions
+3 -1
View File
@@ -19,7 +19,9 @@ import Layout from "../layouts/Layout.astro";
const seen = new Set<string>(); const seen = new Set<string>();
return value.filter(item => item && typeof item === "object" && typeof item.key === "string" && item.key.startsWith("/spots/")).map(item => { return value.filter(item => item && typeof item === "object" && typeof item.key === "string" && item.key.startsWith("/spots/")).map(item => {
const source = item as Record<string, unknown>; const source = item as Record<string, unknown>;
return Object.fromEntries(["key", "waterbody", "coordinates", "baits", "method", "retrieve", "risk", "freshness", "confidence"].map(key => [key, planText(key, source[key])])); const entry = Object.fromEntries(["key", "waterbody", "coordinates", "baits", "method", "retrieve", "risk", "freshness", "confidence"].map(key => [key, planText(key, source[key])]));
if (!/^\d{1,3}$/.test(entry.confidence) || Number(entry.confidence) > 100) entry.confidence = "";
return entry;
}).filter(item => { }).filter(item => {
if (seen.has(item.key)) return false; if (seen.has(item.key)) return false;
seen.add(item.key); seen.add(item.key);
+6
View File
@@ -67,6 +67,11 @@ test("plan preserves a real zero confidence and bounds imported text", async ({
method: "Спиннинг", method: "Спиннинг",
retrieve: "равномерная", retrieve: "равномерная",
oversized: "x".repeat(5000), oversized: "x".repeat(5000),
}, {
key: "/spots/vyunok-7450x7451",
waterbody: "Вьюнок",
coordinates: "7450:7451",
confidence: "999",
}]))); }])));
await page.reload(); await page.reload();
@@ -74,6 +79,7 @@ test("plan preserves a real zero confidence and bounds imported text", async ({
const stored = await page.evaluate(() => JSON.parse(localStorage.getItem("rf4spotter:fishing-plan") || "[]")); const stored = await page.evaluate(() => JSON.parse(localStorage.getItem("rf4spotter:fishing-plan") || "[]"));
expect(stored[0].key.length).toBeLessThanOrEqual(512); expect(stored[0].key.length).toBeLessThanOrEqual(512);
expect(stored[0].confidence).toBe("0"); expect(stored[0].confidence).toBe("0");
expect(stored[1].confidence).toBe("");
expect(stored[0].oversized).toBeUndefined(); expect(stored[0].oversized).toBeUndefined();
}); });
+1 -1
View File
@@ -96,7 +96,7 @@
- [ ] **U02 · Главный сценарий «рыба → водоём → точка → снасть».** Главная сохраняет рыбу, водоём, период и сортировку в shareable URL, явно показывает контекст запроса и даёт текстовый CTA «Открыть точку» на каждой карточке, включая mobile; первый экран ограничен пятью вариантами, а остальные доступны через сохраняющую query-контекст серверную пагинацию; empty-state предлагает вернуться к полному набору данных. Осталось добавить режим map. Acceptance: первый полезный вариант виден без регистрации, back/refresh сохраняют контекст, mobile не теряет фильтры. - [ ] **U02 · Главный сценарий «рыба → водоём → точка → снасть».** Главная сохраняет рыбу, водоём, период и сортировку в shareable URL, явно показывает контекст запроса и даёт текстовый CTA «Открыть точку» на каждой карточке, включая mobile; первый экран ограничен пятью вариантами, а остальные доступны через сохраняющую query-контекст серверную пагинацию; empty-state предлагает вернуться к полному набору данных. Осталось добавить режим map. Acceptance: первый полезный вариант виден без регистрации, back/refresh сохраняют контекст, mobile не теряет фильтры.
- [ ] **U03 · Evidence/trust card.** Общий evidence-контракт используется на activity-карточках, detail точки, водоёма и карточках снастей: freshness с текстовым `Свежо`/`Устарело`, явный период расчёта, completeness, confidence/статус, source badges и доступные доменные поля; при выборке меньше 3 явно показано отдельное состояние `Недостаточно данных`, ограниченный источник не смешивается с неполными полями, а явно переданные provenance-конфликты видны текстом. Targeted E2E и API-тест проверяют публичные пути, период, 48-часовой порог и конфликт источников; единый `passportDisplayStatus` дополнительно закрепляет приоритет `conflict``stale` → исходный статус unit-регрессией. - [ ] **U03 · Evidence/trust card.** Общий evidence-контракт используется на activity-карточках, detail точки, водоёма и карточках снастей: freshness с текстовым `Свежо`/`Устарело`, явный период расчёта, completeness, confidence/статус, source badges и доступные доменные поля; при выборке меньше 3 явно показано отдельное состояние `Недостаточно данных`, ограниченный источник не смешивается с неполными полями, а явно переданные provenance-конфликты видны текстом. Targeted E2E и API-тест проверяют публичные пути, период, 48-часовой порог и конфликт источников; единый `passportDisplayStatus` дополнительно закрепляет приоритет `conflict``stale` → исходный статус unit-регрессией.
- [ ] **U04 · List/map и progressive disclosure.** List остаётся честным базовым режимом: фильтры, сортировка, URL-состояние и evidence-карточки уже работают без имитации координатной карты. Следующий шаг — единый list/map-контракт после подтверждения геометрии; на mobile карта должна открываться отдельным действием. Вторичные raw/provenance-поля не исчезают и раскрываются по запросу. - [ ] **U04 · List/map и progressive disclosure.** List остаётся честным базовым режимом: фильтры, сортировка, URL-состояние и evidence-карточки уже работают без имитации координатной карты. Следующий шаг — единый list/map-контракт после подтверждения геометрии; на mobile карта должна открываться отдельным действием. Вторичные raw/provenance-поля не исчезают и раскрываются по запросу.
- [ ] **U05 · Mobile-first и сохранённый план рыбалки.** `/plan` поддерживает список до 5 уникальных локальных вариантов, удаление, очистку, переход к точке, print/PDF и восстановление из shareable URL; кнопка «Поделиться планом» использует native share или clipboard fallback и явно сообщает, если план пуст или share-ссылка повреждена. Detail-кнопка сохраняет данные с `aria-pressed` и восстанавливается после reload. Print/mobile-контракт проверяет 320/390 px, лимит импорта и отсутствие горизонтального overflow; импорт дедуплицирует точки, нормализует длины локальных текстовых полей, не теряет реальное значение confidence `0`, а карточка плана сравнивает подтверждённые метод/проводку и явно показывает риск по выборке/уверенности. - [ ] **U05 · Mobile-first и сохранённый план рыбалки.** `/plan` поддерживает список до 5 уникальных локальных вариантов, удаление, очистку, переход к точке, print/PDF и восстановление из shareable URL; кнопка «Поделиться планом» использует native share или clipboard fallback и явно сообщает, если план пуст или share-ссылка повреждена. Detail-кнопка сохраняет данные с `aria-pressed` и восстанавливается после reload. Print/mobile-контракт проверяет 320/390 px, лимит импорта и отсутствие горизонтального overflow; импорт дедуплицирует точки, нормализует длины локальных текстовых полей, принимает confidence только в диапазоне `0..100` и не теряет реальное значение `0`, а карточка плана сравнивает подтверждённые метод/проводку и явно показывает риск по выборке/уверенности.
- [ ] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, provenance и качество собраны в общем паспорте данных, а подтверждённые метод/проводка видны рядом с последними уловами; статусы дополнительно передаются текстом, малая выборка не маркируется как готовая рекомендация, а дублирующие catches/players/confidence убраны из вторичной колонки activity-карточки. Mobile route-contract проверяет первый ответ и действие на home, spot, waterbody, plan и tackle, а task regression требует source/evidence-поля; остаётся ручной review визуальных деталей по [ux-task-review-2026-09-21.md](ux-task-review-2026-09-21.md). - [ ] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, provenance и качество собраны в общем паспорте данных, а подтверждённые метод/проводка видны рядом с последними уловами; статусы дополнительно передаются текстом, малая выборка не маркируется как готовая рекомендация, а дублирующие catches/players/confidence убраны из вторичной колонки activity-карточки. Mobile route-contract проверяет первый ответ и действие на home, spot, waterbody, plan и tackle, а task regression требует source/evidence-поля; остаётся ручной review визуальных деталей по [ux-task-review-2026-09-21.md](ux-task-review-2026-09-21.md).
- [ ] **U07 · UX-приёмка и измерения.** Playwright-контракты уже покрывают query journey, сквозной task journey, evidence states, saved plan, share/print, accessibility и visual matrix; unit-контракт добавил детерминированные stale- и blocked-подписи, а insufficient-data теперь отделён от incomplete в публичном паспорте. `/status` явно сообщает, что при ограничении источника опубликованные данные сохранены. Visual-matrix regression теперь проверяет `204` комбинации, включая spot detail, plan и tackle analytics; representative review зафиксирован в `docs/ux-reference-review-2026-09-21.md`. Остаётся получить production Lighthouse/CLS/INP/time-to-first-useful-answer после пилота и принять решение о долговременном хранении screenshots. - [ ] **U07 · UX-приёмка и измерения.** Playwright-контракты уже покрывают query journey, сквозной task journey, evidence states, saved plan, share/print, accessibility и visual matrix; unit-контракт добавил детерминированные stale- и blocked-подписи, а insufficient-data теперь отделён от incomplete в публичном паспорте. `/status` явно сообщает, что при ограничении источника опубликованные данные сохранены. Visual-matrix regression теперь проверяет `204` комбинации, включая spot detail, plan и tackle analytics; representative review зафиксирован в `docs/ux-reference-review-2026-09-21.md`. Остаётся получить production Lighthouse/CLS/INP/time-to-first-useful-answer после пилота и принять решение о долговременном хранении screenshots.