diff --git a/PRODUCT.md b/PRODUCT.md
index 8c3ae02..4e8a67c 100644
--- a/PRODUCT.md
+++ b/PRODUCT.md
@@ -10,7 +10,7 @@ product
## Product Purpose
-Приложение подключает одно или несколько выбранных окон, подтверждает открытое торговое окно по его нижнему элементу, читает тип сделки и имя торговца из заголовка магазина и накапливает названия и цены из tooltip при ручном наведении на предметы. Каждое распознавание сохраняется с датой и периодически отправляется в home-service. Успех прототипа означает, что весь путь от выбора окна до видимого и сохранённого результата работает локально на macOS и Windows.
+Приложение подключает одно или несколько выбранных окон, подтверждает открытое торговое окно по его нижнему элементу, читает тип сделки и имя торговца из заголовка магазина и накапливает названия и цены из tooltip при ручном наведении на предметы. Основной экран показывает компактные блоки торговцев, а каждое новое распознавание сохраняется с датой и сразу отправляется в черновик рынка home-service. Успех прототипа означает, что весь путь от выбора окна до видимого и сохранённого результата работает локально на macOS и Windows.
## Brand Personality
@@ -23,7 +23,7 @@ product
## Design Principles
- Показывать реальный захваченный кадр и реальный результат, а не скрывать состояние процесса.
-- Вести пользователя по прямому пути: источник, калибровка, результат.
+- Вести пользователя по прямому пути: экран, парсинг, сохранённый черновик. Калибровка остаётся редкой настройкой.
- Сохранять интерфейс компактным и предсказуемым при нескольких окнах.
- Оставлять настраиваемыми только параметры, которые зависят от реального изображения.
diff --git a/README.md b/README.md
index de17e28..171b566 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ npm run dev
└─ да → OCR точной строки названия и точной строки цены
```
-Заголовок и зона tooltip хранятся относительно маркера магазина. Поэтому всё торговое окно можно перемещать. Широкая зона tooltip используется только для поиска `Price :` и не передаётся целиком в OCR. После нахождения якоря приложение отдельно определяет фактическую ширину строк названия и цены по их светлым пикселям.
+Заголовок и зона tooltip хранятся относительно маркера магазина. Поэтому всё торговое окно можно перемещать. Широкая зона tooltip используется только для поиска `Price :` и не передаётся целиком в OCR. После нахождения якоря приложение отдельно определяет фактическую ширину строк названия и цены по пикселям текста; для названия учитываются белый, синий и фиолетовый цвета.
Из заголовка `Private Store(Buy) - Hikvision` получится `side: "buy"`, `merchant: "Hikvision"`; из `Private Store(Sell) - Dwa` — `side: "sell"`, `merchant: "Dwa"`.
@@ -55,18 +55,13 @@ npm run dev
## Сценарий
-1. В разделе «Источники» нажми «Добавить окно» для каждого окна игры.
-2. В разделе «Калибровка» сделай снимок или загрузи готовый скриншот.
-3. Выдели постоянный нижний элемент торгового окна, например `Adena + Confirm`, как маркер магазина. По возможности не захватывай изменяемые числа.
-4. Выдели заголовок магазина одной строкой: от `Private Store(` до конца имени торговца. Рамку и кнопку закрытия не включай.
-5. Выдели широкую горизонтальную полосу всех возможных положений tooltip. Она может выходить далеко за пределы торгового окна, но не должна захватывать нижний `Price` основного окна.
-6. Внутри tooltip обведи только постоянную надпись `Price :`.
-7. Отдельно выдели левый край, высоту и начальную ширину строки названия и строки цены. Runtime сам продлит каждую строку вправо до фактического конца текста.
-8. Сохрани калибровку, нажми «Начать сбор» и вернись в игру.
-9. Сначала оставь курсор вне предметов, пока в результате не появятся тип сделки и имя торговца.
-10. Наводи курсор на каждый предмет. Даже если tooltip закроет заголовок, предмет будет записан в последний найденный магазин.
+1. В разделе «Экран» нажми «Добавить окно» и выбери окно игры. Приложение сразу откроет «Парсинг».
+2. Нажми «Начать парсинг» и вернись в игру.
+3. Сначала оставь курсор вне предметов, пока не появится блок торговца с типом сделки и именем.
+4. Наводи курсор на каждый предмет. Товары заполняют блок торговца, следующий торговец появляется справа или на новой строке.
+5. Калибровку открывай как дополнительную настройку, если текущая общая конфигурация не распознаёт окно или tooltip.
-Наблюдения сначала попадают в IndexedDB браузера, затем отправляются пакетами раз в 30 секунд. При недоступном сервере пакет остаётся локально и повторяется с тем же ID после восстановления связи или перезапуска страницы; сервер не создаёт дубликат.
+Новое наблюдение сначала попадает в IndexedDB браузера и сразу отправляется на сервер; 30-секундный повтор остаётся страховкой. При недоступном сервере пакет остаётся локально и повторяется с тем же ID после восстановления связи или перезапуска страницы. Сервер хранит исходный пакет без дублей и для распознанных каталожных предметов обновляет отдельный черновик рынка: `Sell` становится `sell_offer`, `Buy` становится `buy_order`. Неопознанные товары остаются только в исходном пакете.
Калибровку можно сделать по снимкам разного размера и с торговым окном в разных местах:
@@ -78,7 +73,7 @@ npm run dev
После выбора всех шести областей нажми «Проверить снимки». Они обрабатываются по порядку: сначала кадр с видимым заголовком запоминает магазин, затем кадр с tooltip добавляет предмет. Подключать игровое окно для этого не нужно.
-Над подробными результатами выводятся уникальные строки вида `Покупка · Lui · Animal Skin · 18 шт. · 400 Adena`. Количество читается из числовых скобок в конце названия, например `(18)` или `(5,600)`; если их нет, используется `1`. Повторное распознавание той же комбинации торговца, типа сделки, предмета, количества и цены не создаёт новую строку.
+Результат состоит только из блоков торговцев. В каждом блоке показаны тип сделки и последние распознанные цена/количество каждого предмета. Количество читается из числовых скобок в конце названия, например `(18)` или `(5,600)`; если их нет, используется `1`. Повторное распознавание той же комбинации не создаёт новую запись.
Во вкладке приложения OCR-название проходит неточный поиск по активному каталогу. Уверенное совпадение заменяется каноническим названием и показывается с иконкой предмета; слабое совпадение остаётся с вопросительным знаком и пометкой «Не найден в каталоге». Предметы группируются по имени торговца и типу сделки.
@@ -120,7 +115,7 @@ npm run dev
- Источники выбираются вручную после каждого обновления страницы.
- Одна калибровка рассчитана на один масштаб интерфейса Lineage II.
-- Сбор работает, пока открыта страница прототипа и он не остановлен кнопкой «Остановить сбор».
+- Парсинг работает, пока открыта страница прототипа и он не остановлен кнопкой «Остановить парсинг».
- Курсор перемещает пользователь, приложение не управляет игрой и не эмулирует ввод.
- Токен импорта хранится только в локальном Vite-процессе; в браузерный bundle он не попадает.
diff --git a/docs/goals/merchant-hud-and-location-capture/GOAL.md b/docs/goals/merchant-hud-and-location-capture/GOAL.md
new file mode 100644
index 0000000..d4a4bc7
--- /dev/null
+++ b/docs/goals/merchant-hud-and-location-capture/GOAL.md
@@ -0,0 +1,12 @@
+# Goal: Merchant HUD And Location Capture
+
+Use Krypton Execution to execute `docs/goals/merchant-hud-and-location-capture/PLAN.md`.
+
+Core rules:
+- Treat `PLAN.md` as the source plan.
+- Preserve the existing recognition loop and use its `result` as the only live HUD truth.
+- Start with one Document Picture-in-Picture HUD; do not add a native wrapper unless its documented limitations block the real workflow.
+- Capture a merchant location only after an explicit user action and inline preview.
+- Store one bounded JPEG per shop visit and link item observations with the same `shopVisitId`.
+- Preserve auth, same-origin writes, catalog ownership and append-only observation batches.
+- Capture target-perspective evidence only with fresh manual-testing permission; otherwise report `implemented but visually unproven`.
diff --git a/docs/goals/merchant-hud-and-location-capture/PLAN.md b/docs/goals/merchant-hud-and-location-capture/PLAN.md
new file mode 100644
index 0000000..64eb6d8
--- /dev/null
+++ b/docs/goals/merchant-hud-and-location-capture/PLAN.md
@@ -0,0 +1,159 @@
+# Merchant HUD And Location Capture Implementation Plan
+
+**Intent:** Дать игроку спокойную оперативную обратную связь поверх окна Lineage II и позволить вручную сохранить примерное место торговца вместе с посещением магазина.
+**Current Behavior:** Состояние распознавания видно только в большом окне аудита. Превью уже умеет рисовать найденные области и прогресс иконок, но при возврате в игру оно скрыто. Сервер сохраняет OCR-наблюдения и общую калибровку, но отдельного посещения магазина и изображения места нет.
+**Expected Outcome:** Из аудита открывается один компактный always-on-top HUD. Он использует уже существующее состояние выбранного источника и показывает торговца, сторону сделки, прогресс предметов и последнюю распознанную позицию. Пользователь может снять текущий кадр, увидеть сжатую миниатюру и явно сохранить её как место текущего посещения магазина.
+**Target-Perspective Output:** Игрок возвращается в Lineage II и поверх окна видит `Покупка · Dwa`, сетку с найденными и ожидающими иконками и последнюю цену. После `Снять место` он видит миниатюру, подтверждает сохранение и получает устойчивый статус `Место сохранено`.
+**Truth Owner:** Браузерный `result` владеет только живым состоянием HUD и текущим `shopVisitId`. PostgreSQL в `home-service` владеет сохранённым посещением и его JPEG. Каталог `home-service` остаётся владельцем канонических имён и иконок предметов.
+**Contract Boundary:** Parser отправляет authenticated same-origin JSON с UUID посещения, торговцем, стороной, временем, исходными размерами и JPEG data URL. Каждое OCR-наблюдение получает необязательный `shopVisitId`, чтобы связать товары с тем же посещением.
+**Cutover:** HUD читает тот же `result`, что текущие source preview и results; отдельного второго состояния распознавания не создаётся. После появления HUD существующие маленькие status chips в source preview остаются fallback внутри страницы, но перестают быть основным игровым feedback path.
+**Displaced Path:** Постоянное переключение из игры в большую вкладку `Результат` для контроля процесса демотируется до подробной диагностики. Автоматическая или покадровая запись мест не добавляется.
+**Value Density:** Первый срез использует нативный Document Picture-in-Picture вместо Electron/Tauri и сохраняет только один подтверждённый сжатый кадр на посещение.
+**Acceptance Evidence:** В поддерживаемом Chrome/Edge игрок открывает HUD, возвращается в игру, видит неизменного торговца при tooltip, наблюдает переход одного слота из ожидания в найденный, снимает место и после server acknowledgement видит сохранённый статус. В PostgreSQL находится одна строка посещения с тем же `shopVisitId`; связанное наблюдение содержит тот же ID.
+**Evidence Lane:** Автоматически: parser unit tests, HUD model tests, contracts/backend route tests, migration smoke, parser/frontend/backend builds и `git diff --check`. Вручную только после отдельного разрешения: реальное always-on-top поведение над Lineage II, читаемость и полезность сжатого снимка.
+**Kill Criteria:** Не создавать второй recognition loop, не копировать OCR-состояние в независимый store, не сохранять full-resolution кадры, не снимать экран автоматически, не добавлять native wrapper до доказанного ограничения PiP. Если требуется несколько независимых overlay одновременно или click-through поверх каждого окна игры, остановить web-срез и отдельно спланировать Windows companion.
+**Architecture Slice:** `l2-market-parser` владеет HUD, visit lifecycle и компрессией кадра; shared contract, BFF, Nest service и PostgreSQL table живут в `home-service`; ingress меняется только для bounded screenshot body.
+**Plan Review Gate:** Requires PRE review before execution.
+
+## Physical Scene And Interface Contract
+
+Игрок работает вечером на большом мониторе с несколькими оконными клиентами Lineage II. HUD лежит поверх текущего клиента и должен читаться на любом игровом фоне, не требуя переключения внимания.
+
+- Один solid dark utility surface примерно `320 × 420`, без прозрачного стекла, игровых рамок и декоративной анимации.
+- Верхняя строка: выбранный источник и состояние сбора.
+- Главная строка: `Покупает/Продаёт · торговец`. Она занимает фиксированное место и не прыгает при OCR.
+- Сетка `6 × 3`: каноническая иконка для найденного предмета, нейтральная ячейка для ожидающего, понятный `?` для неоднозначного.
+- Последний результат: название, количество, цена и короткий статус совпадения с каталогом.
+- Действие `Снять место` создаёт inline-миниатюру. Рядом появляются `Сохранить` и `Переснять`; modal не используется.
+- Успех, ошибка и ожидание меняют текст/цвет в фиксированном status slot. Движение только для смены состояния, 150–200 ms, без layout animation.
+- Document PiP открывается только по нажатию пользователя и только в secure context. Если API недоступен, большая страница продолжает работать и объясняет, что нужен актуальный Chrome/Edge.
+- Web-страница не может задавать позицию PiP и поддерживает только одно такое окно. Пользователь размещает его сам; HUD показывает выбранный источник.
+
+## Architecture Map
+
+### Files to create
+
+`l2-market-parser`:
+
+- `src/market-hud.js`: одно представление HUD из существующего `result`, открытие/закрытие Document PiP и fallback-status.
+- `test/market-hud.test.js`: модель торговца, прогресс слотов, последний предмет и unsupported fallback.
+
+`home-service`:
+
+- Следующая свободная `backend/migrations/*.sql`: таблица `l2_market_shop_visits`.
+- Соответствующий `backend/migrations/meta/*_snapshot.json` и `_journal.json` только через текущий Drizzle workflow.
+- `frontend/src/app/api/l2/market/visits/route.ts`: authenticated same-origin BFF `PUT`.
+
+### Files to modify
+
+`l2-market-parser`:
+
+- `index.html`: кнопка `Поверх игры` и минимальные fallback/status hooks.
+- `src/main.js`: передача выбранного `result` в HUD, lifecycle одного `shopVisitId`, захват текущего источника и явное сохранение миниатюры.
+- `src/frame-analyzer.js`: новый visit ID при подтверждённой смене/повторном открытии магазина; очистка ID вместе с transient shop progress; добавление ID в observation.
+- `src/style.css`: общие HUD tokens и компактная layout-сетка без отдельной визуальной системы.
+- `src/parser.js`: чистый helper экспорта HUD/visit данных, только если он уменьшает дублирование.
+
+`home-service`:
+
+- `contracts/src/l2.ts`: strict `L2MarketShopVisitSchema`, bounded JPEG data URL, размеры и optional `shopVisitId` у observation.
+- `backend/src/database/schema.ts`: `l2MarketShopVisits` с UUID, временем, source, side, merchant, dimensions, bounded image text и indexes.
+- `backend/src/l2/market/l2-market.controller.ts`: `PUT /l2/market/visits/:id`.
+- `backend/src/l2/market/l2-market.service.ts`: idempotent insert/update одного пользовательски подтверждённого кадра посещения.
+- `backend/src/l2/market/l2-market.service.spec.ts` и controller spec: strict validation, idempotency и linkage ID.
+- `frontend/src/lib/l2-session.spec.ts`: unauthenticated/cross-origin rejection и authenticated forwarding.
+- `docs/goals/l2-craft-farm-planner/l2-vault.nginx`: exact visits route с bounded body limit только если итоговый JSON превышает текущие 16 KiB.
+- `frontend/public/l2/market-audit/**`: только сгенерированный production bundle после parser build.
+
+### Files to avoid
+
+- Не менять catalog matching, OCR, calibration geometry и quote/snapshot semantics.
+- Не хранить изображение внутри каждого item observation или каждого retry batch.
+- Не трогать другие home-service сайты, admin access и backend public port.
+- Не добавлять Electron, Tauri, React/state library, object storage или image processing dependency.
+
+### Source of truth and paths
+
+- **Read path:** `state.results` → `buildHudModel(result)` → один PiP document. Каталожные имена/иконки уже приходят через существующий fuzzy resolver.
+- **Screenshot path:** выбранный source video → canvas downscale → JPEG quality reduction → inline preview → explicit confirm → BFF → Nest → PostgreSQL.
+- **Observation link:** `activateShop` создаёт `shopVisitId`; каждое последующее observation этого магазина несёт ID; закрытие магазина после существующего miss policy очищает ID.
+- **Compression ceiling:** начать с max width `640`, JPEG quality `0.35`; при превышении contract size уменьшать quality/width до bounded payload. Сохранять исходные и итоговые размеры.
+- **Write semantics:** повторный `PUT` с тем же visit ID заменяет только подтверждённую миниатюру и capture metadata; OCR observations остаются append-only batch payloads.
+
+## Tasks
+
+### 1. Зафиксировать visit lifecycle и shared contracts
+
+**Allowed scope:** `src/frame-analyzer.js`, parser tests, `contracts/src/l2.ts`, market service tests.
+
+- Добавить `shopVisitId` и `shopVisitStartedAt` в transient result.
+- Создавать ID только при первом подтверждённом заголовке после reset или реальной смене shop key.
+- Не менять ID, пока tooltip закрывает заголовок.
+- Добавить optional ID в observation и strict visit screenshot contract.
+
+**Verification:** `npm test`; focused contracts build and market specs.
+
+**Acceptance:** тест воспроизводит header → tooltip → несколько items и доказывает один visit ID и неизменного торговца.
+
+### 2. Построить один PiP HUD из существующего result
+
+**Allowed scope:** `market-hud.js`, `main.js`, `index.html`, `style.css`, HUD test.
+
+- Открывать Document PiP только по явному нажатию.
+- Подключить модель выбранного source result без второго timer/recognition loop.
+- Рендерить merchant, side, slot progress, icons, unresolved state и last item.
+- Перерисовывать HUD после текущего `drawSourceOverlay`/result update.
+- Закрытие PiP не останавливает сбор; закрытие основной страницы закрывает PiP нативно.
+
+**Verification:** parser unit tests and production build.
+
+**Acceptance:** mocked supported/unsupported window tests; модель сохраняет фиксированную геометрию при idle/found/error.
+
+### 3. Добавить ручной capture с inline preview
+
+**Allowed scope:** parser HUD/main code and tests.
+
+- `Снять место` доступно только при active source и подтверждённом merchant/visit ID.
+- Захватить один текущий frame, downscale/compress без новой зависимости.
+- До отправки показать миниатюру, размер и `Сохранить/Переснять`.
+- После ошибки оставить миниатюру для повторного сохранения; не делать background screenshots.
+
+**Verification:** deterministic canvas compression test with a generated fixture; payload always satisfies contract limit.
+
+**Acceptance:** одна кнопка создаёт один preview; cancel/reshoot не пишет сервер.
+
+### 4. Сохранить место торговца в home-service
+
+**Allowed scope:** migration/schema, shared contract, market controller/service, exact BFF route and tests.
+
+- Создать bounded visit table and index by merchant/seenAt.
+- Реализовать authenticated same-origin `PUT` and idempotent backend write.
+- Не принимать PNG, произвольный MIME, oversized base64 или mismatched path/body ID.
+- Не возвращать изображение из unrelated snapshot/catalog endpoints.
+
+**Verification:** Drizzle migration generation/check, market service/controller specs, frontend BFF test, contracts/backend/frontend builds.
+
+**Acceptance:** database smoke verifies one replaceable visit row and observation payload with the same `shopVisitId`.
+
+### 5. Интегрировать и доказать cutover
+
+**Allowed scope:** generated audit bundle, relevant README/evidence, exact ingress body rule if required.
+
+- Пересобрать parser directly into `frontend/public/l2/market-audit`.
+- Проверить bundle на отсутствие embedded tokens/full-resolution fixtures.
+- Programmatic public route check remains password-gated.
+- Не объявлять игровой overlay доказанным без отдельного manual permission.
+
+**Verification:** both repository builds, focused tests, `git diff --check`, migration smoke.
+
+**Acceptance:** automated lane green; target lane recorded as either proven over a real game window or `implemented but visually unproven`.
+
+## PRE Self-Review
+
+- **Mode:** PRE
+- **Verdict:** aligned
+- **Blockers:** none for the web prototype.
+- **Major constraint:** Document PiP gives one user-positioned always-on-top window, not multiple click-through overlays attached to game windows. This is explicit in UX and kill criteria.
+- **Major security constraint:** screenshots may contain chat/account information; capture stays manual with inline preview, strict JPEG/size validation and authenticated storage.
+- **Smallest next gate:** owner accepts the one-PiP limitation and manual screenshot confirmation, then execution starts with Task 1.
+
diff --git a/index.html b/index.html
index 2d00408..28083ed 100644
--- a/index.html
+++ b/index.html
@@ -28,13 +28,13 @@
@@ -43,8 +43,8 @@