docs: add mvp plan
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
# План начальной реализации: минимальный рабочий MVP
|
||||
|
||||
Сопутствующие документы: [roadmap.md](./roadmap.md) — общий план по фазам, [analysis.md](./analysis.md) — перечень утилит.
|
||||
|
||||
## 0. Зафиксированные решения
|
||||
|
||||
- **Объём MVP:** только одиночные инструменты «загрузил → настроил → скачал». Пайплайн-workspace (`/workspace`) — сразу следующим шагом после checkpoint'а, не внутри MVP.
|
||||
- **Набор инструментов первого среза:** ~11 базовых EASY (см. §3). Достаточно, чтобы проверить насквозь ядро → реестр → универсальную страницу.
|
||||
- **Стили:** обычный CSS без фреймворка — design-токены на CSS custom properties, ноль зависимостей.
|
||||
- **Стек:** SvelteKit + `@sveltejs/adapter-static` + TypeScript, пакетный менеджер pnpm (уже зафиксирован в корневом `package.json`). Весь код сайта — в `web/`.
|
||||
- **Ядро без DOM:** все пиксельные операции — чистые функции над `PixelImage`; DOM/canvas допускается только в модуле IO и компонентах превью.
|
||||
|
||||
---
|
||||
|
||||
## 1. Шаг 0 — Каркас проекта
|
||||
|
||||
1. Создать SvelteKit-приложение в `web/`: `npx sv create web` (шаблон minimal, TypeScript, без лишних аддонов).
|
||||
2. `pnpm install` внутри `web/`.
|
||||
3. `svelte.config.js`: заменить адаптер на `@sveltejs/adapter-static`.
|
||||
4. `src/routes/+layout.ts`: `export const prerender = true;` — весь сайт prerenderится в статику.
|
||||
5. `src/app.css`: дизайн-токены (палитра, типографика, отступы, радиусы через CSS custom properties) + минимальный сброс стилей.
|
||||
6. `src/routes/+layout.svelte`: шапка с названием сайта и навигацией по категориям инструментов (категории — константа из реестра), футер.
|
||||
|
||||
**Проверка:** `pnpm dev` открывает пустой каркас; `pnpm build && pnpm preview` отдаёт статический экспорт.
|
||||
|
||||
---
|
||||
|
||||
## 2. Шаг 1 — Ядро: типы и ввод-вывод
|
||||
|
||||
Файлы в `web/src/lib/core/`:
|
||||
|
||||
- `types.ts`
|
||||
|
||||
```ts
|
||||
export type PixelImage = { width: number; height: number; data: Uint8ClampedArray }; // RGBA, как ImageData
|
||||
```
|
||||
|
||||
- `io.ts` — единственное место ядра, где разрешён canvas/DOM:
|
||||
- `decodeFile(file: File): Promise<PixelImage>` — `createImageBitmap(file)` → отрисовка на canvas → `getImageData`;
|
||||
- `encode(img: PixelImage, mime: 'image/png' | 'image/jpeg' | 'image/webp', quality?: number): Promise<Blob>` — `putImageData` → `canvas.toBlob`;
|
||||
- `downloadBlob(blob: Blob, filename: string): void`.
|
||||
|
||||
**Проверка:** юнит-смоук — декодировать фикстурный PNG, закодировать обратно, размеры совпадают.
|
||||
|
||||
---
|
||||
|
||||
## 3. Шаг 2 — Операции ядра (чистые функции)
|
||||
|
||||
Файлы в `web/src/lib/core/`, каждая функция: `(img, params) => new PixelImage`, без мутации входа:
|
||||
|
||||
- `color.ts`: `grayscale(img)` (luma), `invert(img)`, `brightnessContrast(img, brightness −100..100, contrast −100..100)`.
|
||||
- `geometry.ts`: `flip(img, axis)`, `rotate90(img, turns)`, `crop(img, x, y, w, h)` — индексная математика; `resize(img, w, h)` — билинейная интерполяция на TS (не `drawImage`, чтобы ядро осталось DOM-free и воспроизводимым бит-в-бит).
|
||||
- `alpha.ts`: `removeColorToAlpha(img, hex, tolerancePercent)` — цветовое расстояние → альфа 0; остальное — позже.
|
||||
- `analyze.ts`: `imageInfo(img)` → `{ width, height, hasAlpha, colorCount }`.
|
||||
|
||||
**Проверка:** vitest на маленьких матрицах пикселей (2×2, 3×3): flip, rotate, crop, grayscale — ожидаемые значения руками.
|
||||
|
||||
---
|
||||
|
||||
## 4. Шаг 3 — Реестр инструментов
|
||||
|
||||
`web/src/lib/registry.ts` — источник истины для страниц, форм и навигации:
|
||||
|
||||
```ts
|
||||
export type ParamDef =
|
||||
| { id: string; label: string; type: "number"; min?: number; max?: number; step?: number; default: number }
|
||||
| { id: string; label: string; type: "select"; options: { value: string; label: string }[]; default: string }
|
||||
| { id: string; label: string; type: "checkbox"; default: boolean }
|
||||
| { id: string; label: string; type: "color"; default: string };
|
||||
|
||||
export type ToolEntry = {
|
||||
id: string; // slug маршрута
|
||||
title: string;
|
||||
description: string;
|
||||
category: CategoryId;
|
||||
params: ParamDef[];
|
||||
run: (img: PixelImage, params: Record<string, unknown>) => Promise<PixelImage> | PixelImage;
|
||||
};
|
||||
```
|
||||
|
||||
Категории (константа там же): `convert`, `alpha`, `color`, `geometry`, `analyze`.
|
||||
|
||||
### Состав первого среза — 11 инструментов
|
||||
|
||||
| # | id | Категория | Параметры |
|
||||
| --- | -------------------------------- | --------- | ----------------------------------------------------- |
|
||||
| 1 | `resize-png` | geometry | width, height, checkbox «сохранять пропорции» |
|
||||
| 2 | `crop-png` | geometry | x, y, width, height |
|
||||
| 3 | `rotate-png` | geometry | select: 90 / 180 / 270 |
|
||||
| 4 | `flip-png` | geometry | select: horizontal / vertical |
|
||||
| 5 | `grayscale-png` | color | — |
|
||||
| 6 | `invert-colors-png` | color | — |
|
||||
| 7 | `adjust-brightness-contrast-png` | color | brightness −100..100, contrast −100..100 |
|
||||
| 8 | `convert-png-to-jpg` | convert | background color (подложка под альфу), quality 0..100 |
|
||||
| 9 | `convert-png-to-webp` | convert | quality 0..100 |
|
||||
| 10 | `remove-color-from-png` | alpha | hex color, tolerance % |
|
||||
| 11 | `png-info` | analyze | — особый случай: не картинка, а текстовая сводка |
|
||||
|
||||
Для `png-info` в записи реестра добавляется флаг `resultType: 'info'`; остальные — `'image'`.
|
||||
|
||||
**Проверка:** импорт реестра в тесте — все id уникальны, у каждого `params` имеют дефолты, `run` определён.
|
||||
|
||||
---
|
||||
|
||||
## 5. Шаг 4 — Универсальные UI-компоненты
|
||||
|
||||
`web/src/lib/components/`:
|
||||
|
||||
- `DropZone.svelte` — drag&drop + клик; эмитит `File`; проверка MIME (PNG/JPEG/WebP/GIF/BMP).
|
||||
- `Preview.svelte` — рендер `PixelImage` на canvas с fit-масштабированием; шахматный фон под прозрачность.
|
||||
- `ParamForm.svelte` — генерирует контролы из `ParamDef[]`, двусторонний `bind:values`.
|
||||
- `DownloadButton.svelte` — encode выбранного формата + `downloadBlob`, имя `<tool-id>.<ext>`.
|
||||
- `InfoPanel.svelte` — текстовый вывод для `resultType: 'info'`.
|
||||
- `ToolPage.svelte` — оркестратор страницы инструмента; состояния `idle → loaded → processing → done | error`; раскладка: слева DropZone, справа превью + форма параметров + кнопки «Применить» / «Скачать».
|
||||
|
||||
**Правило:** новый инструмент = новая запись в реестре (+ функция операции). Компоненты и маршрут не трогаем.
|
||||
|
||||
---
|
||||
|
||||
## 6. Шаг 5 — Маршруты
|
||||
|
||||
- `/` (`src/routes/+page.svelte`) — сетка карточек инструментов, сгруппированных по категориям; данные из реестра.
|
||||
- `/tools/[id]` — универсальная страница:
|
||||
- `+page.ts`: `entries()` возвращает все `id` из реестра (обязательное условие prerender при adapter-static);
|
||||
- `load()` возвращает запись реестра по `id`, неизвестный id → 404;
|
||||
- `+page.svelte` — тонкая обёртка над `ToolPage`.
|
||||
|
||||
**Проверка:** `pnpm build` — в `build/tools/` лежат все 11 страниц; главная содержит ссылки на каждую.
|
||||
|
||||
---
|
||||
|
||||
## 7. Шаг 6 — Ошибки и минимальная полировка
|
||||
|
||||
- Обработка ошибок: неподдерживаемый формат, ошибка декода, некорректные параметры crop (выход за границы — клампы или понятная ошибка).
|
||||
- Состояния: «перетащите файл», спиннер во время обработки, disabled-кнопки до загрузки.
|
||||
- Тексты описаний для всех 11 инструментов (title/description уже в реестре).
|
||||
|
||||
---
|
||||
|
||||
## 8. Шаг 7 — Верификация и checkpoint
|
||||
|
||||
Команды:
|
||||
|
||||
```bash
|
||||
cd web && pnpm dev # разработка
|
||||
cd web && pnpm build # статический экспорт должен проходить без ошибок
|
||||
cd web && pnpm test # vitest: ядро (геометрия/цвет) + целостность реестра
|
||||
```
|
||||
|
||||
Ручной smoke-тест каждого из 11 инструментов: загрузка PNG → применить → скачать → открыть результат.
|
||||
|
||||
**Checkpoint MVP:** любой из 11 инструментов доступен по прямому URL с главной страницы; PNG загружается drag&drop; параметры применяются; результат скачивается; всё работает офлайн со статического хостинга; ни одной серверной зависимости.
|
||||
|
||||
---
|
||||
|
||||
## 9. Сразу после MVP (не входит в план)
|
||||
|
||||
По roadmap.md §Фаза 1, в порядке следования:
|
||||
|
||||
1. Пайплайн-workspace `/workspace` (шаги, превью шагов, localStorage, экспорт/импорт JSON).
|
||||
2. Расширение набора до ~40 топовых EASY из analysis.md §6.2.
|
||||
3. Web Worker для тяжёлых операций, полировка UX, SEO-описания.
|
||||
|
||||
Что сознательно отложено: wasm/Rust, эталонные файлы, Playwright, CLI — фазы 2+ из roadmap.md.
|
||||
Reference in New Issue
Block a user