mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 12:49:56 +00:00
docs: #39 spec revision 3 per SPEC-REVIEW-39-r1
Issue: #39 User-Visible: no
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/39
|
||||
- Приоритет: P3 (триаж владельца 2026-08-15), полный трек
|
||||
- Ревизия: 2 (2026-08-29) — research-фаза проведена, константы предложены по бенчмарку
|
||||
- Ревизия: 3 (2026-08-29) — по SPEC-REVIEW-39-r1: сценарий, i18n-ключи, риски, release-артефакты, двухфазный hard
|
||||
|
||||
## Цель
|
||||
|
||||
@@ -10,6 +10,21 @@
|
||||
память планшета/WebView, и предлагать уменьшенную копию — не трогая текущий
|
||||
план и оригинал файла при любой ошибке.
|
||||
|
||||
## Сценарий
|
||||
|
||||
Владелец дома настраивает план с планшета или ноутбука: в диалоге пространства
|
||||
выбирает «Файл» и указывает скан поэтажного плана — обычно это фото или скан
|
||||
150–600 DPI, который «как есть» весит десятки мегапикселей. Сегодня такой выбор
|
||||
молча вешает вкладку на слабом планшете (полный decode + тройная конвертация в
|
||||
base64 до каких-либо проверок).
|
||||
|
||||
**До**: выбрал большой файл → вкладка замирает или падает без объяснений;
|
||||
план, который был на экране, можно потерять вместе с вкладкой.
|
||||
**После**: выбрал большой файл → мгновенно появляется понятный диалог «Файл
|
||||
очень большой (10000×10000, 58 МБ, потребует ~380 МБ памяти)» с кнопкой
|
||||
«Загрузить уменьшенную копию»; одно нажатие — и на плане та же картинка, но
|
||||
безопасного размера. Текущий план в любом исходе остаётся цел.
|
||||
|
||||
## Текущее состояние (анкеры кода)
|
||||
|
||||
`_pickPlanFile` (`houseplan-editor-runtime.ts:8243`) принимает PNG/JPEG/WebP/SVG
|
||||
@@ -76,15 +91,49 @@ fail-closed к диалогу, не к тихому продолжению. SVG
|
||||
- `warn` — диалог (`hp-dialog`, паттерн существующих подтверждений): разрешение,
|
||||
размер файла, оценка decoded-памяти; действия «Загрузить уменьшенную копию»
|
||||
(основное), «Оставить оригинал», «Отмена». Числа — из probe, до decode.
|
||||
- `hard` (сторона > 16384, decode-ошибка или таймаут 10 с на этапе
|
||||
уменьшения) — только «Отмена» + совет уменьшить файл на десктопе; текущая
|
||||
подложка и staging не меняются.
|
||||
- `hard` — ДВЕ фазы с разным моментом наступления и одним исходом
|
||||
(«ничего не изменилось»):
|
||||
- **фаза 1, синхронная** (сторона > 16384 по заголовку, до какого-либо
|
||||
decode): диалог `backdrop.too_large_*` с единственной кнопкой «Отмена» и
|
||||
советом уменьшить файл на десктопе;
|
||||
- **фаза 2, асинхронная** (пользователь выбрал «Уменьшенную копию», а decode
|
||||
упал или не уложился в таймаут 10 с): warn-диалог закрывается, показывается
|
||||
тост `backdrop.downscale_failed`; поле выбора файла сброшено, staging
|
||||
чист — пользователь может выбрать «Оставить оригинал», повторив выбор
|
||||
файла, либо уменьшить файл сам. Автоматического фолбэка на оригинал нет:
|
||||
молча грузить то, от чего пользователь только что отказался, нечестно.
|
||||
- Уменьшение: `createImageBitmap(file, {imageOrientation: 'from-image'})` (EXIF
|
||||
учтён) → OffscreenCanvas (fallback `<canvas>`), aspect сохраняется; alpha из
|
||||
probe: PNG/WebP c alpha → PNG, opaque → JPEG q0.9 (WebP-энкод не берём —
|
||||
Safari не пишет). Уменьшенный Blob идёт тем же путём planFile → upload →
|
||||
квота/copy-on-write, что и оригинал.
|
||||
- Тексты en/ru/de; диалог на мобильной ширине без горизонтального скролла.
|
||||
- Диалог на мобильной ширине без горизонтального скролла.
|
||||
|
||||
## i18n (ключи en/ru; de — перевод тех же ключей)
|
||||
|
||||
- `backdrop.large_title` — "Large image" / «Большое изображение»;
|
||||
- `backdrop.large_body` — "This image is {w}×{h} ({fileMb} MB file) and needs
|
||||
about {decodedMb} MB of memory to display. On tablets this can crash the
|
||||
page." / «Изображение {w}×{h} ({fileMb} МБ), для показа потребуется около
|
||||
{decodedMb} МБ памяти. На планшетах это может привести к падению страницы.»;
|
||||
- `backdrop.unknown_body` — "The image dimensions could not be read — the file
|
||||
may be damaged. Continuing may crash the page on tablets." / «Не удалось
|
||||
прочитать размеры изображения — файл может быть повреждён. Продолжение может
|
||||
привести к падению страницы на планшете.»;
|
||||
- `backdrop.use_downscaled` — "Upload a reduced copy" / «Загрузить уменьшенную
|
||||
копию»;
|
||||
- `backdrop.keep_original` — "Keep the original" / «Оставить оригинал»;
|
||||
- `backdrop.too_large_title` — "Image is too large" / «Изображение слишком
|
||||
большое»;
|
||||
- `backdrop.too_large_body` — "This image exceeds what browsers can display
|
||||
({w}×{h}, limit {limit} px per side). Please reduce it in a desktop editor
|
||||
and upload again." / «Изображение превышает возможности браузера ({w}×{h},
|
||||
предел {limit} px по стороне). Уменьшите его в редакторе на компьютере и
|
||||
загрузите снова.»;
|
||||
- `backdrop.downscale_failed` — "Could not create the reduced copy. The
|
||||
original plan was not changed." / «Не удалось создать уменьшенную копию.
|
||||
Текущий план не изменён.»;
|
||||
- `toast.plan_formats` — существующий, без изменений.
|
||||
|
||||
## Транзакция (без изменений, закрепляется тестом)
|
||||
|
||||
@@ -103,8 +152,11 @@ issue = оригинальный ФАЙЛ пользователя никогд
|
||||
2. «Уменьшенная копия»: длинная сторона 4096, aspect сохранён; PNG с alpha →
|
||||
PNG (alpha жива), opaque → JPEG; результат уходит существующим upload-путём.
|
||||
3. «Оставить оригинал» — прежнее поведение байт-в-байт.
|
||||
4. `hard`: сторона >16384 либо decode-fail/таймаут — только Отмена; текущая
|
||||
подложка, staging и конфиг не изменились.
|
||||
4. `hard` фаза 1: сторона >16384 из заголовка — диалог «слишком большое» с
|
||||
одной «Отменой», без decode; подложка/staging/конфиг не изменились.
|
||||
4б. `hard` фаза 2: decode-fail или таймаут 10 с после выбора «Уменьшенной
|
||||
копии» — тост `backdrop.downscale_failed`, staging чист, подложка/конфиг не
|
||||
изменились, повторный выбор файла работает.
|
||||
5. SVG: только прежняя валидация, растеризации нет.
|
||||
6. Битые/усечённые заголовки (PNG без IHDR, JPEG без SOF, WebP-огрызок) →
|
||||
`unknown` → warn-диалог без чисел; продолжение возможно только явным выбором.
|
||||
@@ -126,6 +178,33 @@ window.createImageBitmap). Мутанты реестра: (1) probe всегда
|
||||
`hard` понижен до warn — смок красный; (4) staging не чистится при отказе —
|
||||
тест транзакции красный.
|
||||
|
||||
## Риски
|
||||
|
||||
- **Пороги не с reference-планшета**: калибровка десктопная с консервативным
|
||||
запасом; ложные срабатывания дешевы (лишний диалог), пропуски дороже — при
|
||||
полевых жалобах правится один модуль констант. Смягчение: числа печатаются в
|
||||
диалоге, жалобу легко диагностировать по скриншоту.
|
||||
- **Зоопарк заголовков** (прогрессивный JPEG, WebP VP8L, EXIF в необычном
|
||||
месте): парсер обязан отвечать `unknown`, не бросать — fail-closed к
|
||||
warn-диалогу; таблица юнит-фикстур покрывает все подвиды из спецификаций
|
||||
форматов.
|
||||
- **OffscreenCanvas недоступен** (старые WebView): фолбэк на `<canvas>` в том
|
||||
же кадре; ветка покрыта юнитом через подмену глобала.
|
||||
- **Regress существующего upload-пути**: safe-путь меняет только механизм
|
||||
base64 (паритет-юнит AC9) — поведенчески байт-в-байт.
|
||||
|
||||
## Release-артефакты
|
||||
|
||||
- CHANGELOG.md + CHANGELOG.ru.md — одна запись (User-Visible: yes);
|
||||
- docs/BACKDROP.md — раздел «Большие изображения» (пороги, поведение, совет);
|
||||
- docs/USER-GUIDE.md + USER-GUIDE.ru.md — абзац в теме подложек;
|
||||
- docs/TESTING.md — чек-лист сценариев (warn/уменьшение/hard×2/SVG);
|
||||
- i18n en/ru/de — ключи из раздела i18n (паритет-тесты стерегут);
|
||||
- golden: не задевается (диалог не входит в golden-матрицу; скриншоты
|
||||
документации пересъёмка capture по общему правилу src-правок);
|
||||
- performance: бенчмарк-скрипт `demo/benchmark_backdrop_decode.mjs` в repo,
|
||||
в перф-джобу CI НЕ добавляется (разовая калибровка, не регресс-гейт).
|
||||
|
||||
## Вне скоупа
|
||||
|
||||
Автопревращение SVG в растр; серверная перекодировка; изменение
|
||||
|
||||
Reference in New Issue
Block a user