Author SHA256 Message Date
AidarKC daa516babe Регистрация: ожидание подтверждения Solana 2026-07-17 18:31:05 +04:00
AidarKC 2890cdd457 Добавить экран проверки public Solana RPC 2026-07-16 20:33:48 +04:00
AidarKC e4dfb43b5e Переименовать server2 deploy-скрипт 2026-07-16 19:19:59 +04:00
AidarKC 3926d561c0 Доработать финал регистрации для мобильного UI 2026-07-16 19:03:04 +04:00
AidarKC 85133db483 Удалить проверенные pending-фичи 2026-07-16 12:15:22 +04:00
AidarKC 6fc7d75e17 Улучшить финал регистрации и автовход 2026-07-16 12:08:59 +04:00
AidarKC be321813fa Убрать test.shineup.me из deploy-схемы 2026-07-16 11:54:21 +04:00
AidarKC d2c0ecdf62 Исправить регистрацию и навести порядок в UI deploy 2026-07-16 11:46:42 +04:00
AidarKC 24cca1f1c6 UI: доработка регистрации и документации 2026-07-15 16:16:24 +04:00
AidarKC 118be418b5 Обновить диагностику звонков и документацию деплоя 2026-07-14 20:07:31 +04:00
AidarKCandClaude Fable 5 2a4c2c64c9 ИТХ: вход в график закрытий по доверию, а не по депозиту
- Из TODO убран депозитный/платный вход — сознательно отвергнут.
- Зафиксирована модель: DAO ведёт список как временный уровень,
  пока не сформировано сообщество сияющих; целевое состояние —
  приём/исключение серверов через голосование сияющих.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 19:57:12 +04:00
AidarKCandClaude Fable 5 172f007e1e Дополнить дизайн ИТХ: форки, списки доверия, децентрализация
- Раздел 6: правило признания — правило чтения (дефолт rota PDA,
  наблюдатель может считать по своему списку доверия); параллельные
  группы закрытий разрешены by design.
- Новый раздел 10 «Форки пользовательских цепочек»: fork-choice
  дежурного (длиннейшая ветка + tie-break), зачекпоинченное побеждает,
  заморозка chain_frozen только при противоречии архивов двух групп,
  рестарт с login-002, байт-в-байт повтор для клиентов.
- Шаг 9.9 проверки: конфликт -> принять версию чекпоинта (один
  пользователь больше не срывает закрытие дня).
- TODO: хэш-выбор дежурного, вход по депозиту, механизм цепочки -002.
- CHANGELOG блокчейна дополнен; формат блоков и AddBlock не менялись.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 18:50:41 +04:00
AidarKC 4cb98f7ca0 Обновить UI чатов и связей и добавить документацию ИТХ 2026-07-14 13:39:01 +04:00
AidarKC af12d7b954 Перенести документацию в docs и добавить социальный граф 2026-07-13 12:58:41 +04:00
AidarKC c9cfb394d7 Добавить ответы в DM и обновить UI чатов 2026-07-10 19:27:49 +04:00
AidarKC 0d2d7b6dc5 Добавить SHiNE теги в DM и безопасный рендер превью 2026-07-10 18:57:44 +04:00
AidarKC 076b43e542 Сделать DM body opaque для сервера и улучшить fallback UI 2026-07-10 18:12:17 +04:00
AidarKC cc1f78106d Обновить backup-снимок production сервера 2026-07-10 12:32:17 +04:00
AidarKC 5a5e9c01ce Обновить UI чата и закрыть проверенные DM задачи 2026-07-10 12:26:36 +04:00
AidarKC 0fb1147eb7 Зафиксировать накопленные изменения по deploy и devnet 2026-07-10 11:57:33 +04:00
AidarKC 2b23aa7b95 Исправить UI статусы DM 2026-07-10 11:57:00 +04:00
AidarKC bb92cb6b2b Исправить восстановление DM после перелогина 2026-07-10 11:47:39 +04:00
AidarKC 0730837ad7 Изолировать страницу DEVNET пополнения от mainnet 2026-07-08 20:28:29 +04:00
AidarKC 98a2631590 Довести DM v1 до соответствия коду и документации 2026-07-08 17:28:39 +04:00
AidarKC faada9dcdb Добавить временную миграцию v11 для очистки старых DM 2026-07-07 20:26:16 +04:00
AidarKC 1b7a2a8f7c Реализовать SHiNE_DM v1 с E2EE и tombstone 2026-07-07 18:07:10 +04:00
AidarKC 9c588bd9b5 Документировать новый DM-протокол и формат (в коде пока не реализовано) 2026-07-07 01:19:04 +04:00
AidarKC 31563fe9ed Добавить исторические файлы создания реального DAO 2026-07-07 01:18:16 +04:00
AidarKC 81bef1e1cc Синхронизировать изменения проекта 2026-07-05 18:18:03 +04:00
AidarKC 0240db59ee Подготовка перед деплоем в mainnet 2026-07-01 12:23:30 +04:00
AidarKC ab4dab34aa Исправить Ed25519 promo-подпись в UI 2026-06-30 18:54:39 +04:00
AidarKC 63f13a4c29 Сократить promo-аргументы регистрации в Solana 2026-06-30 18:40:33 +04:00
AidarKC 0a416a2f5c Добавить promo-регистрацию и оффлайн генератор промокодов 2026-06-30 17:39:40 +04:00
AidarKC b0887648f7 Обновить документацию и словари 2026-06-30 11:39:31 +04:00
AidarKC 5720f1cb50 Переименовать экран предоплаченного места 2026-06-29 08:45:30 +04:00
AidarKC 9324da5cb7 Разделить покупку билета на два экрана 2026-06-28 15:11:05 +04:00
AidarKC 408b0eeb39 Закрыть проверку remote AddBlock через homeserver 2026-06-28 14:49:57 +04:00
AidarKC ed83b1f906 Переделать remote AddBlock на сборку блока на homeserver 2026-06-28 14:45:29 +04:00
AidarKC 3068c3e2b8 Добавить раздел поддержки проекта Сияние 2026-06-28 13:53:12 +04:00
AidarKC 93c6f247f7 Исправить ответный SendSignal на ESP32 2026-06-28 12:29:01 +04:00
AidarKC 05a9441493 Исправить подпись SendSignal в UI 2026-06-28 12:09:13 +04:00
AidarKC aa02e92e4d Добавить SendSignal и remote AddBlock через homeserver 2026-06-28 11:20:51 +04:00
AidarKC c397c28acb Удалить старый мусор из документации 2026-06-28 10:29:27 +04:00
AidarKC c93cc6c522 Перенести backlog в TODO 2026-06-28 09:30:59 +04:00
AidarKC 0cdcc77606 Добавить TODO с планами на будущее 2026-06-26 17:55:05 +04:00
AidarKC 87eec7e5c9 Зафиксировать успешную синхронизацию на тестовом сервере 2026-06-26 17:46:34 +04:00
AidarKC 44a1ba01f3 Починить восстановление blockchain_state при full resync 2026-06-26 17:30:10 +04:00
AidarKC d49661fa29 Исправить хэш коммита в changelog блокчейна 2026-06-26 17:06:05 +04:00
AidarKC 71fdee0cfd Вернуть crash-safe запись AddBlock через tmp_bch 2026-06-26 17:05:37 +04:00
AidarKC 1ced351ea2 Закрыть проверенные pending-фичи 2026-06-26 16:56:57 +04:00
AidarKC c048347f2e Добавить startup recovery для resync цепочек 2026-06-26 15:51:52 +04:00
AidarKC be4f76834a Добавить resync блокчейна при рассинхроне 2026-06-26 15:25:11 +04:00
AidarKC 23edad416c Добавить sync-профиль пользователя и обход Solana RPC 2026-06-25 18:46:47 +04:00
AidarKC f3e4233285 Исправить загрузку внешнего application.properties 2026-06-25 18:04:04 +04:00
AidarKC 84e0f039cb Добавить периодический sync блокчейнов каждые 12 часов 2026-06-25 17:58:07 +04:00
AidarKC 1f8b20a7d1 Добавить ListBlockchainHeads для межсерверной сверки 2026-06-25 17:52:04 +04:00
AidarKC f0e1ab3af8 Перевести тестовый контур на t.shineup.me 2026-06-25 13:44:22 +04:00
AidarKC 112ab4d5d5 Улучшить звуки входящих личных сообщений 2026-06-25 12:55:42 +04:00
AidarKC 8768e142e3 Исправить мобильный ввод в личном чате 2026-06-25 11:20:30 +04:00
AidarKC 827d2e9c3e Улучшить открытие личного чата 2026-06-25 10:55:17 +04:00
AidarKC 0f3c4a621d Улучшить UX личного чата на мобильных 2026-06-25 10:46:45 +04:00
AidarKC e60475f351 Обновить синхронизацию серверов и экран сохранения ключей 2026-06-24 20:18:40 +04:00
AidarKC 0f63f7dae6 Добавить документацию по Figma 2026-06-24 16:30:16 +04:00
AidarKC f9a15ab192 Обновить список каналов и кнопку сообщения 2026-06-24 14:59:08 +04:00
AidarKC 77f5759d60 Обновить UI кошелька и регистрацию 2026-06-24 13:48:07 +04:00
AidarKC 684f3237cf Исправить подключение и подпись в браузерном кошельке 2026-06-24 10:28:54 +04:00
AidarKC 23e61cc182 ESP32 регистрация и архив тестового удаления PDA 2026-06-23 20:31:05 +04:00
AidarKC d2f45ff67a ESP32: приостановить reconnect на экранах регистрации 2026-06-23 19:19:18 +04:00
AidarKC 06e12e9103 ESP32: убрать ранние checkpoint при add homeserver 2026-06-23 19:12:58 +04:00
AidarKC 29dddeff4f ESP32: убрать временный автозапуск homeserver 2026-06-23 19:03:32 +04:00
AidarKC 017d568aea ESP32: автозапуск Add Homeserver для отладки 2026-06-23 18:56:33 +04:00
AidarKC c91b52cfd2 ESP32: checkpoint перед чтением PDA homeserver 2026-06-23 18:46:41 +04:00
AidarKC 2bd38d8d78 ESP32: диагностический checkpoint для update homeserver 2026-06-23 18:39:58 +04:00
AidarKC 7d9db68d80 ESP32: NTP для update user_pda 2026-06-23 18:32:32 +04:00
AidarKC 4b94303d67 ESP32: server login и NTP для регистрации 2026-06-23 18:11:11 +04:00
AidarKC 08628704c7 UI: добавить просмотр любого PDA 2026-06-23 17:05:57 +04:00
AidarKC f1c1132690 AuthChallenge: поддержать RecoveryKeyBlock 2026-06-23 17:04:47 +04:00
AidarKC d2426c473c ESP32: убрать кэш генерации секрета 2026-06-23 14:42:02 +04:00
AidarKC 66986b804c Вынести дефолтный сервер в конфиг UI и оживить счетчик пароля 2026-06-23 14:11:09 +04:00
AidarKC 95daa230bb Обновить серверный UI под recovery key 2026-06-23 13:44:18 +04:00
AidarKC 365b22d778 ESP32: кэш последних генераций секрета 2026-06-23 12:06:24 +04:00
AidarKC cf2b54464e Исправить update user_pda для homeserver на ESP32 2026-06-23 11:42:44 +04:00
AidarKC 4e60c1274a ESP32: ускорить и упростить secret screen 2026-06-23 11:08:19 +04:00
AidarKC 2f65e63fbe ESP32: новая derivation ключей 2026-06-23 10:51:03 +04:00
AidarKC b461431197 Смена адреса shine_users и выкладка на test2 2026-06-23 10:40:52 +04:00
AidarKC 5c92b6a734 Миграция PDA на client.key 2026-06-22 21:57:09 +04:00
AidarKC ba348dafb3 Add Wallet Standard registration for browser wallet 2026-06-22 01:35:26 +04:00
AidarKC ce2d310e8c ESP32 wallet RPC, browser wallet provider, and side panel 2026-06-22 01:30:08 +04:00
AidarKC 475db28095 main: это именно та версия, которая уже стоит на тестовом продакшен сервере
Да, звучит немного удивительно, но именно так: люди уже могут регистрироваться, писать друг другу личные сообщения, звонить и пользоваться каналами.

При этом регистрация пока идёт через тестовые Solana/devnet, так что это всё ещё этап тестирования. И в UI ещё куча всего не доделана.

Но тем не менее всё равно.

Прикольно.
2026-06-21 13:14:02 +04:00
AidarKC 823a41c027 chore idea vcs mappings cleanup 2026-06-21 13:10:51 +04:00
AidarKC 2a834f1b14 fix ui dm chatid lowercase normalization 2026-06-21 12:27:41 +04:00
AidarKC c8ffb6cf29 Настроить test2 как основной контур деплоя 2026-06-20 23:34:41 +04:00
AidarKC ecc9efd434 UI: скрыть пароль при режиме 12 слов 2026-06-20 21:33:44 +04:00
AidarKC dd35e56029 Добавить временную бесплатную загрузку аватаров в Arweave 2026-06-20 21:29:35 +04:00
AidarKC d0e7998650 UI: обновить экраны входа 2026-06-20 20:15:40 +04:00
AidarKC fec5e49304 UI: FAQ регистрации и режим пароля из 12 слов 2026-06-20 19:05:45 +04:00
AidarKC 3b12e14e71 Docs: добавить идею homeserver команд и обмена файлами 2026-06-20 17:21:47 +04:00
AidarKC 86eaf2139d UI: улучшить личные сообщения и поиск контактов 2026-06-20 17:19:32 +04:00
AidarKC 65fad993ad UI: вернуть старую вкладку Личные и починить аватары в Связях 2026-06-20 16:43:53 +04:00
AidarKC 55e6e477be merge(main): объединить esp-and-wallet и UI Pixel 2026-06-20 12:17:11 +04:00
PixelandClaude Opus 4.8 ba5efcc152 Merge: UI «Связи» (финал) + редизайн «Личные» (чистый прод) в main
- Граф «Связи»: обновлён до финальной версии поверх PR #3 «pixel-связи»
  (орбы 12/13.06, единый PNG-оверлей орбов, мягкий край, вибрация выключена).
- «Личные»: редизайн списка как формы «Связей» — фото-аватары/инициалы,
  золотая галочка подтверждения у имени, значок-цепочка связи с попапом пути
  (Ты → посредники → цель) и переходом в профиль, граница карточки по типу
  связи, шапка «Shine». Данные пока мок-плейсхолдер (реальные relations/чаты —
  отдельная задача с бэкендом).
- Чистый прод: сняты обе демо-лаборатории (граф/ЛС), demo-чат, гость-обвязка
  ЛС и demo-avatars; экран «Личные» под логином.
- Сохранена работа агента в main (DM-ревизии/редактирование, wallet/pairing, esp32).
- VERSION: client 1.2.217 (server 1.2.204 без изменений).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 23:10:59 +03:00
PixelandClaude Opus 4.8 26253564d5 chore: bump client 1.2.169
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:31:10 +03:00
PixelandClaude Opus 4.8 92791c77a9 прод: убрать ЛС-лабу (demo-чат, гость-доступ, isDemo, demo-avatars); мок→плейсхолдер
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:31:09 +03:00
PixelandClaude Opus 4.8 465792b2ab прод: убрать граф-лабу (network/lab.js, selftest.js, граф-мок в mock-data)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:31:09 +03:00
PixelandClaude Opus 4.8 de269fd828 chore: bump client 1.2.168 + .gitignore (.claude, бэкап-ассеты)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:48:28 +03:00
PixelandClaude Opus 4.8 8c91484f37 nav: вкладка «Личные» (лейбл)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:48:27 +03:00
PixelandClaude Opus 4.8 6904ac8b7c ЛС: редизайн списка (фото-аватары, галочка/значок связи у имени, попап-цепочка→профиль) + demo-чат и lab-маршрут
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:48:27 +03:00
PixelandClaude Opus 4.8 aea6bbcb0e ЛС: токены связей + резолвер визуала + семантический мок (connectedVia/login)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:48:26 +03:00
AidarKC a788d8bcf5 Обновить pairing устройств и доработать ESP32 UI 2026-06-19 20:47:56 +04:00
AidarKC cc074a941f Исправить маршрутизацию call push по sessionId 2026-06-19 19:18:16 +04:00
AidarKC 47574100f9 Исправить самообрыв звонка и обновить TURN 2026-06-19 18:25:47 +04:00
PixelandClaude Opus 4.8 7ad74942e0 Связи: отключена вибрация в графе
haptic() сделан no-op — на экране «Связи» телефон не вибрирует ни на тапах по узлам,
ни на переходах (раскрытие/погружение/всплытие/пан). Вызовы haptic(...) оставлены, тело пустое.
Версия 1.2.167.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:03:32 +03:00
PixelandClaude Opus 4.8 ac1cc04637 nav: неоновые PNG-иконки вкладок бара + единый вид
- toolbar.js: data-driven иконки (iconImg/glow/hero); все 5 вкладок → <img> неон-PNG;
  «Связи» помечена hero.
- components.css: единый размер (--tab-icon-size 27px), «Связи» крупнее ВИЗУАЛЬНО через
  transform: scale (без сдвига раскладки — иконки на одной линии); active/tap-состояния;
  у «Связи» убран лишний drop-shadow-ореол (светится сама PNG); глобально
  -webkit-tap-highlight-color: transparent (нет синего tap-квадрата нигде).
- assets: icon_lichnye/kanaly/svyazi/uvedomleniya/profil.png. Иконка «Уведомления»
  приведена к прозрачному фону (была без альфы) и обрезана до ~92% заполнения, как у других.
Версия 1.2.166.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:03:04 +03:00
aidar 722d055e2d Merge esp32-2-by-tbilisi into main
Merge ветки esp32-2-by-tbilisi в main: TrustedDeviceLogin, редактирование и удаление личных сообщений, browser wallet extension, аудит Solana smart contracts, удаление устаревшего SHiNE-promo-solana-devnet и закрытых pending features.
2026-06-18 12:21:37 +00:00
AidarKC a9a55da8e0 Удалить закрытые записи pending features 2026-06-18 16:11:36 +04:00
AidarKC f2b23ace8b Удалить устаревший SHiNE-promo-solana-devnet 2026-06-18 15:57:55 +04:00
AidarKC 1f2048e270 Перестроить структуру codex-agent-VPS для VPS-пакета 2026-06-18 15:29:09 +04:00
AidarKC b16a23243e Добавить переносимый шаблон codex-agent-VPS 2026-06-18 15:18:07 +04:00
AidarKC 653f1268a6 Проверено: DM-ревизии подтверждены, pending убран 2026-06-18 14:34:37 +04:00
AidarKC 56db6d0add TrustedDeviceLogin API и настройки входа через устройство
Что сделано:\n- публичный API сценария входа через доверенное устройство переведён на TrustedDeviceLogin\n- добавлен GetTrustedDeviceLoginSettings\n- отсутствие записи настроек на сервере теперь трактуется как enabled=true и hasPassword=false\n- ttlSeconds убран из клиентского API, TTL заявки фиксирован на сервере: 300 секунд\n- в shine-UI добавлен отдельный экран настроек входа через устройство и статус на основном экране\n- browser wallet переведён на новые TrustedDeviceLogin операции\n- в wallet добавлен выбор rootKey/deviceKey для будущего запроса подписи\n- документация API обновлена\n\nЧто ещё не проверено вручную end-to-end:\n- полный сценарий UI/plugin после этого деплоя не прогонялся руками до конца\n- сам signaling подписи в wallet всё ещё не реализован
2026-06-18 14:19:31 +04:00
AidarKC cf2152dcfc НЕ ПРОВЕРЕНО: UI редактирования и удаления личных сообщений 2026-06-18 13:04:06 +04:00
AidarKC a95bd245cf НЕ ПРОВЕРЕНО: откат DM-вложений, оставлены ревизии и удаление 2026-06-18 12:24:14 +04:00
AidarKC 92fd315505 НЕ ПРОВЕРЕНО: DM-вложения, upload файлов и ревизии личных сообщений 2026-06-18 11:46:58 +04:00
AidarKC 2225c2d173 Wallet plugin: офлайн wallet-session и выбор homeserver\n\nСделано:\n- wallet plugin сохраняет PDA-профиль и остаётся офлайн до действия;\n- добавлен каркас выбора ключа подписи и homeserver-устройства;\n- добавлен ручной refresh trusted devices через ListSessions;\n- на регистрации показан первый сервер SHiNE и его адрес;\n- обновлены pending notes для ручной проверки.\n\nЕщё не проверено / не доделано:\n- end-to-end ручная проверка plugin после этих правок не завершена;\n- signaling запроса подписи и ответ подписи ещё не реализованы;\n- локальный browser plugin нужно отдельно reload в Chrome/Opera. 2026-06-18 11:04:34 +04:00
AidarKC f8a76bcd7f Автоопределение SHiNE-сервера по логину через PDA 2026-06-16 16:32:33 +04:00
AidarKC 3efa8bb7ee Wallet-session pairing и browser plugin wallet, оплаты пока не работают 2026-06-16 16:23:08 +04:00
AidarKC 5c155ef503 UI: нормальное закрытие сессий и сортировка устройств 2026-06-16 10:36:43 +04:00
AidarKC 41d199e24a Показывать ошибки pairing-пароля в отдельном окне 2026-06-15 15:31:19 +04:00
AidarKC e1f2b54de3 Сузить диалог изменения pairing-пароля 2026-06-15 15:23:19 +04:00
AidarKC d6c5757dfa Переделать UI дополнительного pairing-пароля 2026-06-15 13:35:05 +04:00
AidarKC 9a489801c5 Доработать UX и отмену pairing по коду 2026-06-15 13:13:16 +04:00
AidarKC 9fcdcd087b Убрать QR-заглушку и очищать код после reject 2026-06-15 02:37:26 +04:00
AidarKC af1304022e Исправить синтаксис экрана pairing 2026-06-15 02:30:17 +04:00
AidarKC 7972676eb8 Исправить pairing без пароля и убрать фантомные заявки 2026-06-15 02:21:21 +04:00
AidarKC bef205aec7 Разрешить pairing без доп пароля 2026-06-15 00:54:56 +04:00
AidarKC 49fdbbf7ae Исправить переход со старта на экран выбора входа 2026-06-14 21:33:43 +04:00
AidarKC dd69a52273 Форсировать обновление UI модулей входа 2026-06-14 21:13:22 +04:00
AidarKC c681b4d684 Добавить UI pairing по коду и обновить документацию агента 2026-06-14 20:39:05 +04:00
AidarKC b166013707 Бот: починить resume-вызов Codex CLI 2026-06-14 20:30:17 +04:00
AidarKC 3e04727022 Добавить ESP pairing через доверенные сессии 2026-06-14 18:21:23 +04:00
AidarKC 5d13112b00 ESP32: уменьшить рамку wallet QR 2026-06-14 11:22:11 +04:00
AidarKC 373f88086e ESP32: подправить вертикальный ритм wallet QR 2026-06-14 11:16:49 +04:00
AidarKC 05492306c0 ESP32: смягчить SHiNE reconnect при плохом сервере 2026-06-14 11:01:47 +04:00
AidarKC 423d490939 ESP32: доработать home экран и wallet QR 2026-06-14 10:50:31 +04:00
AidarKC 7edc0ba901 ESP32: зафиксировать LVGL qrcode конфиг 2026-06-14 10:28:42 +04:00
AidarKC 0ebb71daf1 ESP32: добавить реальный wallet QR через LVGL 2026-06-14 10:27:10 +04:00
PixelandClaude Opus 4.8 b4480d89cf css: убрать закомментированный блок кластерной ауры (финально)
Решение по ауре финальное — не возвращаем. Удалён закомментированный «ТЕСТ»-блок
box-shadow по категориям; оставлено явное box-shadow:none для сияющих/фокуса.
Версия 1.2.165.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:13:17 +03:00
PixelandClaude Opus 4.8 ff584ba5d1 orb: мягкий край фото + убран фон-градиент категории из-под стекла
- растушёвка края фото радиальной маской (--feather-full 62% / --feather-edge 78%):
  фото сливается со стеклом без жёсткого ободка; параметры для подкрутки.
- убран фон-градиент категории на .node-dot (просвечивал через прозрачный центр
  стекла → читался как цветная «обводка»): селектор поднят до (0,4,0), чтобы
  перебить правила категории. Цвет категории остаётся на линиях.
- кластерная аура (box-shadow по категории) отключена.
Не тронуто: кромка PNG, свечение сияющих/фокуса/common, линии.
Версия 1.2.164.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:55:09 +03:00
AidarKC 4b15cabd4f ESP32: добавить быстрый QR-экран кошелька 2026-06-13 23:20:35 +04:00
AidarKC be4a2d135a Проект: включить ESP32-wallet в основной репозиторий 2026-06-13 23:01:57 +04:00
PixelandClaude Opus 4.8 69f0fdf120 orb: единый PNG-оверлей на все узлы + ретайр вектора
- glass_overlay_faithful.png в assets; орбы = фото (низ, круглая маска 78%) +
  стеклянный PNG (верх, бокс 119% от node-dot, контакт линий от ORB_R).
- PNG-оверлей применён ко ВСЕМ полным орбам (центр + спутники); tier-3 точки без изменений.
- ретайр мёртвого векторного стекла: удалён buildGlassOrb (+orbSeq) и CSS .fg-orb-svg,
  снят остаточный border .node-dot (синее кольцо) у PNG-хостов.
Версия 1.2.163.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:21:56 +03:00
PixelandClaude Opus 4.8 e3bebff618 anim (13.06): ускорение разлёта узлов — BLOOM_MS 900→550
Дети «выстреливают» из центра почти вдвое быстрее; easing и каскад (stagger)
прежние. Убирает ощущение тяжести/«подтупливания» при смене центра.
Версия 1.2.162.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:36:46 +03:00
PixelandClaude Opus 4.8 f19f7b0ec4 Связи (13.06): орбы по референсу, линии по категории, постоянная вселенная
Орбы:
- материал «хрусталь»: чистое лицо (виньетка 0.5→0.22), диффузный блик окна
  вместо «капли» (+мягкий blur sf), стекло прозрачнее (тело 0.38→0.3),
  полупрозрачная преломляющая кромка (blur + opacity 0.25→0.2).
- размер +11.5% (node-dot 52→58px); единый ORB_R=29 как источник радиуса.
- убран значок * у общих узлов (логика is-common цела).

Линии:
- цвет по категории на ВСЕХ рёбрах; плазма только сияющим.
- общий узел наследует сияние исходного человека (не серый).
- контакт линий ровно на кромке сферы орба (ORB_R), без зазора, все уровни.

Навигация:
- констелляция (паутина 2-3 уровней) — постоянный режим; кнопка «Вселенная»
  убрана; Семья/Друзья/Сияющие остаются фильтрами. Чистка осиротевшего CSS.

Версия 1.2.161.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:31:46 +03:00
AidarKC ca4cfd9d8d UI: выровнять online-флаг с ответом ListSessions 2026-06-13 15:50:47 +04:00
AidarKC 96d292074b API: добавить online-флаг для ListSessions 2026-06-13 15:49:34 +04:00
AidarKC 0536a018c6 ESP32: починить JSON auth для homeserver sessionType 2026-06-13 15:22:19 +04:00
AidarKC 81d1b84a7d ESP32: отправлять homeserver sessionType в SHiNE auth 2026-06-13 15:08:53 +04:00
AidarKC 61c21b245e UI: явно показать тип сеанса в списке устройств 2026-06-13 15:05:41 +04:00
AidarKC 919387f581 API сессий: добавить sessionType и clientPlatform 2026-06-13 14:15:42 +04:00
AidarKC 3b8ea70d3c ESP32: добавить диагностику подключения SHiNE и починить WS handshake 2026-06-13 13:09:32 +04:00
AidarKC 477ab3b580 ESP32: починить добавление homeserver и вернуть автопрогон 2026-06-13 12:53:40 +04:00
AidarKC a1da814030 ESP32: добавить flow обновления homeserver в user PDA 2026-06-13 09:07:49 +04:00
AidarKC 19fd5611b2 ESP32: добавить автотест регистрации и исправить signed server profile 2026-06-13 08:41:25 +04:00
AidarKC 556004a557 ESP32: исправить off-curve проверку для user PDA 2026-06-13 08:20:12 +04:00
AidarKC fba6d6bba0 ESP32: исправить derivation user_pda для Solana 2026-06-13 07:36:45 +04:00
AidarKC 04252e006b ESP32: сохранять полный текст ошибки регистрации 2026-06-13 00:24:42 +04:00
AidarKC 436e1f0c53 ESP32: добавить USB-диагностику регистрации Solana 2026-06-13 00:01:57 +04:00
AidarKC 21030b1d51 ESP32: исправить base64 сериализацию Solana транзакции 2026-06-12 23:48:38 +04:00
AidarKC b583a86ade ESP32: исправить ABI регистрации и подробные ошибки RPC 2026-06-12 23:43:42 +04:00
AidarKC 3262ec9b4a ESP32: перевести UI регистрации на английский 2026-06-12 23:35:05 +04:00
AidarKC 0c9afea67a ESP32: экран подтверждения регистрации 2026-06-12 23:02:07 +04:00
AidarKC b83543d018 ESP32: регистрация по кнопке и зазор между кнопками 2026-06-12 22:24:21 +04:00
AidarKC d4a0185507 Перенёс основной ESP32-скетч в main-device 2026-06-12 22:02:08 +04:00
AidarKCandClaude Opus 4.8 42dcf6970d homeserver: рендейм subserver→homeserver, документ деривации ключей, запрет пустого пароля
Основное (наша работа в этой сессии):
- Переименование «subserver» → «homeserver» по всему проекту: основной ESP32-скетч
  (папка shine_subserver_ui → shine_homeserver_ui, .ino, flash-скрипт, режим burn.sh
  homeserver-ui), скетч lvgl_nav_minimal_test (ключ homeserver.key:<имя>), spec-доки
  reference/*, формат PDA (терминология session_type=100 «Homeserver пользователя»),
  константа SESSION_TYPE_HOMESERVER в JS и Rust (значение 100 не менялось, формат не затронут),
  pending/future доки, AGENTS.md, DAO-док. Сохранены отдельный lvgl_subserver_touch_test и
  историческая пометка о рендейме в DERIVATION.md.
- Новый источник истины по деривации ключей: Dev_Docs/Keys/DERIVATION.md (Argon2id-секрет из
  пароля, формула Ed25519(SHA-256(base64(secret)|suffix)), суффиксы root/bch/dev/homeserver.key,
  Solana-ключ = dev.key). Уточнены роли root (главный/master) и dev (пополняемый кошелёк) в
  Dev_Docs/Keys/README.md.
- UI: убран легаси-путь пустого пароля (derivePasswordSeed и др.), deriveMasterSecretFromPassword
  бросает ошибку на пустом пароле, register-view блокирует пустой пароль; экран пополнения
  переведён на канонический device-адрес из preGeneratedKeyBundle (удалён расходящийся
  deriveWalletFromPassword).

Включены также параллельные правки Solana-аудита №3 (были в рабочем дереве, переплетены в lib.rs):
- shine_users: defense-in-depth «строгий список аккаунтов» (require!(it.next().is_none()))
  в init/update economy config и create/update user PDA, плюс описание в doc/programs/shine_users.md;
- Dev_Docs/audit/Solana-audit-3-by-Claude-12июня2026.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 21:16:12 +04:00
PixelandClaude Opus 4.8 0b4374141e Связи (test 12.06): центр-орб крупнее (FOCUS_SCALE 1.78) + шире ореол центра (glowSpread 7)
Рычаг 1: glowSpread центра 4.5→7 (мягче/шире свечение), спутники без изменений. Рычаг 2: FOCUS_SCALE 1.5→1.78 (иерархия). Версия 1.2.160.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:53:13 +03:00
PixelandClaude Opus 4.8 652ddc9d88 Связи (test 12.06): SVG-стеклянные орбы аватаров + цвет/свечение линий глубоких связей
Аватары → SVG GlassOrb (фото в стеклянной сфере, блик, rim, свечение). Линии глубоких связей (tier-2/3) — в цвете типа (друзья/семья/...), сияющие связи светятся (голубой ореол + ядро). Версия 1.2.159.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:10:06 +03:00
AidarKCandClaude Fable 5 cf6a2830c8 solana: закрыть griefing создания PDA и заморозку выплат, добавить аудит №2
shine_payments + shine_users:
- create_pda_account переведён на «создание поверх предзаполненного»
  (allocate+assign+добор ренты), чтобы подсев лампортов на детерминированный
  адрес PDA (тикет/логин) не блокировал создание — закрыт LOW из аудита №1;
  в shine_payments is_uninitialized_account перестала зависеть от баланса.

shine_payments (HIGH из аудита №2):
- запрещён recipient == inflow_vault в buy_ticket*, manager_add_ticket и
  change_ticket_recipient; добавлена защита по умолчанию в transfer_from_vault
  (require vault.key != recipient.key). Это убирает алиасинг аккаунта в
  step_payout, который навсегда замораживал очередь выплат и средства вольта.

Документация и учёт:
- doc/programs/shine_payments.md §3.4, §10.1; doc/programs/shine_users.md §3.3;
- Dev_Docs/audit: добавлен аудит №2, обе закрытые находки помечены ИСПРАВЛЕНО;
- Dev_Docs/Pending_Features: две записи на ручную e2e-проверку на devnet;
- VERSION.properties: client 1.2.161, server 1.2.150.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 04:10:31 +04:00
AidarKC 01d9553db4 merge(ui): влить PR #3 pixel-связи в main 2026-06-11 00:24:36 +04:00
AidarKC 578b526f96 Скорректировать main к базе 553a1f1 и UI из Pixel 2026-06-10 23:03:01 +04:00
AidarKC e3061b46f9 Связи: финальный вид сияющих связей
Перенести финальный плазменный рендер связей из коммита Pixel 2559f1e6 в main.
2026-06-10 19:00:22 +04:00
PixelandClaude Opus 4.8 2559f1e66b Связи: финальный вид сияющих связей — плазма (поле+трубка+ядро, screen-blend) + цвет связи у обычных
Промежуточные итерации линий схлопнуты в один коммит. Убран мёртвый фильтр fg-plasma-turb. Лаборатория (/network-view/lab) и автотесты сохранены. Версия 1.2.158.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:09:33 +03:00
PixelandClaude Opus 4.8 519bce6b78 Связи (pixel-aquarium, 10.06): партия 3 (лаборатория) — общие связи + доступность
Вариант 2 (всё в лаборатории, реальный путь /network-view не трогаем).

- Общие связи: среди друзей человека один помечен как «общий» (он и твой друг тоже) — золотой ободок
  + ★ (CSS .fg-node.is-common). В лаб-генерации addDeepLevels подставляет узнаваемого друга Ивана.
- Доступность: визуально скрытый (sr-only) текстовый список графа .fg-a11y (центр + связи 1-го уровня)
  для скринридеров; обновляется в updateA11y при перестроении (role=region, aria-label).

Автопроверки расширены до 19 ассертов (добавлены «общие связи ★» и sr-only список) — прогон 19/19 PASS.
Бамп client.version → 1.2.150.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:53:49 +03:00
PixelandClaude Opus 4.8 557ea96be0 Связи (pixel-aquarium, 10.06): партия 2 (UI-фишки) — поиск, хлебные крошки, бейджи, цветовые кластеры
Всё в лаборатории (вариант 2: реальный путь /network-view не трогаем).

- Поиск + телепорт: строка .fg-search; Enter → graph.findNode(имя) → камера летит к узлу
  (dive в «Вселенной», иначе перецентр).
- Хлебные крошки: .fg-breadcrumb «Иван › Нина › Ада» (движок шлёт onDiveChange(path), API getDivePath);
  клик по корню — полный сброс, по предку — навигация на его уровень.
- Бейдж числа связей: .fg-node-badge (degreeById → updateBadges; у центра — число связей 1-го уровня).
- Цветовые кластеры: мягкая аура узла по типу связи (CSS is-family/friend/business/contact).

Автопроверки расширены до 17 ассертов (добавлены поиск/крошки/бейдж) — прогон 17/17 PASS.
Фикс: TDZ breadcrumbEl (объявлен до createForceGraph, т.к. onDiveChange вызывается при монтировании).
Бамп client.version → 1.2.149.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:43:02 +03:00
PixelandClaude Opus 4.8 9a49cc67f0 Связи (pixel-aquarium, 10.06): партия 1 (полиш) + автопроверки графа
Усиления (движок-полиш) с детерминированной самопроверкой:
- Веер детей — полукругом «наружу» (DEEP_FAN, по sibIndex от направления деда→родитель): не перекрывает
  нить-крошку и родителя; равномерное распределение.
- LOD с гистерезисом (LOD_ZOOM_UP=1.6 / DOWN=1.4) — точки 3-го уровня ↔ аватарки без «мигания» у порога.
- Двойной тап по пустому фону и сильный pinch-out на минимальном зуме = быстрый выход из погружения.
- Префетч аватарок детей при наведении/нырке (prefetchChildren) — лица в кэше до раскрытия.

Автопроверки (dev-only, ТОЛЬКО при ?fgtest):
- js/pages/network/selftest.js — 14 ассертов: камера-центровка, collision (нет слипания), полукруг,
  spotlight (путь 1.0 / фон 0.25 / сброс при переключении / 100% на выходе), LOD, возврат зума.
- Движок: read-only graph.debugState() + graph.pumpForTest() (синхронно докручивает кадры до покоя,
  не зависит от троттлинга rAF в фоне). Граф как window.__fg — тоже только при ?fgtest.
- Прогон: 14/14 PASS (offset 0px, мин.дистанция детей 89px, веер ±99°, LOD 4/4).

В обычной работе тест-хелперы не активны. Реальный путь /network-view не затронут. Бамп client → 1.2.148.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:25:57 +03:00
PixelandClaude Opus 4.8 3012f0799b Связи (pixel-aquarium, 10.06): фикс физики раскрытия + камера-наезд + железный spotlight
Исправлены критические баги «аквариума» по разбору (видео):

1. Слипание узлов → адаптивный радиус орбиты (фикс collision). Дети раскладываются на кольце
   ringR = max(baseR + радиус_родителя, число_детей×13): не лезут на (увеличенный зумом) родитель
   и друг на друга. Проверено: мин. дистанция 125px, 0 наложений (было — все в одной точке).

2. Умный наезд камеры на КЛИК по любому узлу (раньше 1-й уровень раскрывался на месте). diveTo
   центрирует узел (offset ~0), zoom 1.7; узел и дети растут до единого видимого размера
   (HERO_VISUAL/baseScaleOf, DIVE_CHILD_VISUAL) — крупно и читаемо. Наведение остаётся лёгким превью.

3. Железный Spotlight (единый активный путь): diveTo гасит ВСЕ прежние pin/hover, затем раскрывает
   только путь к цели. Открыто → путь=1.0, остальное=0.25; переключение веток сбрасывает прежнюю;
   exitDive/тап по Ивану → ВСЕ узлы гарантированно 1.0 + камера отъезжает. (Проверено программно.)

Реальный путь /network-view не затронут (вся глубина под tier≥2/hasDeep). Бамп client.version → 1.2.147.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:12:27 +03:00
PixelandClaude Opus 4.8 7a8852f64b Связи (pixel-aquarium): Умный фокус (Smart Zoom / «аквариум») — погружение в узел + LOD
Новая ветка для безопасного отката (от pixel-web). Режим «Вселенная», только лаборатория.

1. Гибрид клика: 1-й уровень → раскрытие ветки НА МЕСТЕ (как раньше); 2-й уровень+ → ПОГРУЖЕНИЕ.

2. Dive (умный наезд камеры, «аквариум», без перестройки графа):
   - diveTo(node): пинит весь путь (предки до Ивана), ставит diveTargetId + diveZoom=1.7;
     камера в tick плавно ЛЕТИТ и ЗУМИТ, центрируя узел (DIVE_FLY_K), узел ВЫРАСТАЕТ (×2.1 ~ герой).
   - Глубина (contextTargetOf → depthScale/depthBlur/spotCur, лерп): Иван и боковые ветки
     УМЕНЬШАЮТСЯ (root ×0.55) + уходят в BLUR 3px + тускнеют до 0.25 → задний план «аквариума».
   - Нить-крошка: путь Иван→…→узел (divePathSet/onPath) горит ярким «световодом» — виден путь назад.
   - Всплытие: повтор клика по цели → exitDive (камера/зум плавно к корню); клик по Ивану →
     collapseAll (полный сброс + всплытие).

3. Pinch-to-Zoom + LOD 3-го уровня: при zoom≥1.55 видимые точки 3-го уровня дорисовываются как
   читаемые аватарки (лицо+имя; updateLod/setNodeLod — пере-рендер DOM на пороге), при отдалении —
   обратно в светящиеся точки. Узлам tier-3 добавлены фото-заглушки (pravatar) и имена.

Глубина — фейк-3D через масштаб + CSS-blur (GPU), без WebGL. Реальный путь /network-view не затронут:
dive только tier≥2 (в реале их нет), depthScale/Blur нейтральны по умолчанию, updateLod выходит при !hasDeep.
Бамп client.version → 1.2.146.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 23:32:16 +03:00
PixelandClaude Opus 4.8 f92e6c3cf1 Связи (pixel-web): фикс багов паутины — видимый 2-й уровень, удалён «прицел», toggle+spotlight
Исправлены замечания по видео (режим «Вселенная», только лаборатория):

1. «Невидимые друзья» (узлы 2-го уровня). Раньше — тусклые пустые кружки с инициалом.
   Теперь tier-2 — полноценные аватарки: фото-лицо (pravatar) + имя, DEEP2_SCALE 0.5→0.62
   (≈radius 16px), DEEP2_OPACITY 0.4→0.85; читаемый ободок и подпись (CSS).

2. Мусорный «прицел» (пунктирное кольцо у центра). Полностью удалён из движка:
   элемент .fg-reticle, updateReticle/pulseReticle и все их вызовы, CSS .fg-reticle*.
   На экране только аватарки и линии связей.

3. Логика toggle + spotlight:
   - Повторный клик по раскрытому узлу теперь СВОРАЧИВАЕТ его (isOpen = pinned || expandP>0.5
     → сброс pinned+hovered) — работает, даже если ветка была раскрыта ховером.
   - Spotlight: при закреплённой ветке остальные тускнеют до 0.25 (узлы и линии), фокус и
     закреплённая/наведённая ветка — 100%; плавно через spotCur (lerp, см. spotTargetOf).
   - Клик по центру (Иван) — collapseAll + возврат всему графу 100% яркости.

Реальный путь /network-view не затронут (deep-код под tier≥2/hasDeep). Ветка экспериментальная.
Бамп client.version → 1.2.145.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:55:10 +03:00
PixelandClaude Opus 4.8 72dc83daff Связи (pixel-web): этап 2 паутины — hover-превью + collision + zoom + камера-доводчик + синхро-пульс
Доработка режима «Интерактивная паутина» (только лаборатория, deep-режим «Вселенная»):

Взаимодействие (по запросу): наведение ≠ клик.
- Hover-превью: навёл мышь/палец на узел — его ветка ВРЕМЕННО выплывает; убрал — втягивается.
  (pointerover/out для мыши, pointerdown/up для пальца → onNodeHover → graph.setHover; флаг hovered).
- Фиксация кликом: тап/клик → graph.toggleExpand ставит pinned — ветка остаётся раскрытой и
  после ухода курсора; повторный тап снимает фиксацию. Эффект = pinned || hovered (expandTargetOf).

Этап 2 «Мегамасштаб»:
- Collision-расталкивание: раскрытая ветка усиливает отталкивание соседей 1-го уровня
  пропорционально expandP (EXPAND_REPULSION=2.4) — кластеры разъезжаются, не накладываясь.
- Свободный зум: колесо мыши (onWheel) + щипок двумя пальцами (activePointers/pinching),
  zoom 0.55–2.6 «к точке»; мир — CSS-scale, линии (SVG) пересчитываются в экранных координатах × zoom.
- Камера-доводчик: при фиксации ветки, если её веер упирается в край, камера мягко дотягивается
  (glideCameraTo → camTargetX/Y, lerp CAM_GLIDE_K в tick); любой жест отменяет доводчик.
- Синхро-пульс: сияющие/трековые «световоды» дышат толщиной/размытием 3.6с в такт ободку узла.

Реальный путь /network-view не затронут: deep-код под tier≥2/hasDeep, hover-колбэк даёт только
лаборатория. Ветка экспериментальная (отдельно от pixel-08.06/PR). Бамп client.version → 1.2.144.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:34:26 +03:00
PixelandClaude Opus 4.8 04d9d588e8 Связи (pixel-web): режим «Интерактивная паутина» — раскрытие веток без смены центра
Этап 1 mind-map (только лаборатория, deep-режим «Вселенная»):
- Отмена прыжков в центр: тап по периферийному узлу больше НЕ перецентрирует — он остаётся
  на орбите, а из него раскрывается/сворачивается (toggle) его ветка дальних связей НА МЕСТЕ.
- Глобальный сброс: тап по корню (Иван) рекурсивно сворачивает все раскрытые ветки (collapseAll).
- Глубина скрыта по умолчанию; ветка плавно выплывает (expandP, ~400мс) и втягивается по повтору.
- Мерцающие звёзды 3-го уровня (CSS box-shadow/brightness, десинхрон по узлам) — «созвездие».
- Тактильный отклик navigator.vibrate(): клик при нажатии, серия импульсов на bloom-раскрытие,
  щелчок «гитарной струны» при сильном натяжении нитей свайпом.
- Движок: API toggleExpand/collapseAll; убрана press/hover-логика раскрытия (заменена тапом).

Ветка экспериментальная (отдельно от pixel-08.06/PR), бамп client.version → 1.2.143.
Ещё не сделано (следующие этапы): collision-расталкивание веток, камера-доводчик, zoom,
синхро-пульс линий к сияющим.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:04:48 +03:00
PixelandClaude Opus 4.8 345a21a211 Связи (эксперимент pixel-web): база — микро-взаимодействия + глубина
Фиксирую накопленные черновики как точку отката перед режимом «Интерактивная паутина»:
- press/pan-bend (резиновые нити при свайпе, тактильное «вдавливание» узла);
- глубина 2-3 уровней (прототип «Вселенная», переключатель в лаборатории);
- прогрессивное раскрытие (глубина скрыта, выплывает по нажатию/наведению).

Ветка pixel-web — экспериментальная (отдельно от pixel-08.06/PR), чтобы можно было откатиться.
Бамп client.version → 1.2.142.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:51:48 +03:00
PixelandClaude Opus 4.8 3de992d251 Связи: вернуть лабораторию в ветку (для просмотра графа на моках)
Откат удаления из aed64e7: lab.js снова в репозитории, статический импорт в network-view.js,
строка lab.js убрана из .gitignore. Лаборатория /network-view/lab нужна для демонстрации/проверки
графа на мок-данных без бэкенда. Бамп client.version → 1.2.141.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:18 +03:00
PixelandClaude Opus 4.8 369ef61cab Связи: лаборатория только локально (исключена из репозитория)
- shine-UI/js/pages/network/lab.js убран из git (git rm --cached) и добавлен в .gitignore:
  на сервере лаборатория не нужна (там реальные данные), локально файл остаётся и работает.
- network-view.js: статический импорт lab.js заменён на ДИНАМИЧЕСКИЙ с фолбэком — если файла
  нет (прод/сервер), реальный экран «Связи» не ломается, а заход на /network-view/lab уводит
  на обычный экран. Локально лаборатория грузится как прежде.
- Документация фичи отражает, что lab.js — локальный (в .gitignore).
- Бамп client.version → 1.2.140.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:18 +03:00
PixelandClaude Opus 4.8 e6e96c4b0d Связи: чистка мёртвого кода в движке (lerp, неиспользуемые easing)
- Убран механизм lerpX/lerpY: координаты для отрисовки берутся из n.x/n.y, lerp нигде
  не читался кроме условия заморозки (lerpSettling). Удалены поля, advanceLerp(), EDGE_LERP
  и lerpSettling — граф засыпает чуть раньше (без визуальных изменений; проверено: frozen=true).
- Удалены неиспользуемые cubicBezier() и EASE_BLOOM (easing теперь делает CSS); easeOutCubic
  оставлен (нужен в stepTween для фолбэк-центрирования).
- Документация фичи актуализирована (убрана заметка про lerp как кандидата на чистку).
- Бамп client.version → 1.2.139.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:18 +03:00
PixelandClaude Opus 4.8 dc96033cb1 Связи: двухслойные линии-световоды, живой фон и стеклянные фильтры
- Сияющие связи — двухслойный неоновый «световод»: размытый glow (4px, blur 2px,
  opacity 0.4) + тонкий чёткий core (1.5px, #e0f7fc). Объёмное OLED-свечение,
  линия остаётся изящной. Оба слоя растут синхронно (общий dashoffset).
- Обычные линии — тоньше (1.0–1.2px) и глубокий уход в прозрачность (0.42 → 0.07),
  чтобы матовые связи не спорили с сияющими.
- Живой фон-«небула»: глубокое размытое сине-голубое облако под центром, медленная
  пульсация радиуса/яркости + переливы индиго↔ультрамарин (hue-rotate, 7с).
- Стеклянные чипы фильтров (frosted glass): rgba(255,255,255,0.03) + backdrop blur(12px)
  + граница 0.5px solid rgba(255,255,255,0.1); активный подсвечен сине-голубым.
- Бамп client.version → 1.2.138; документация фичи обновлена.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:17 +03:00
PixelandClaude Opus 4.8 9ee6bf4380 Связи: полировка карты связей (свечение, прорастание линий, CSS-фильтры)
- Линии: тонкие дуги Безье (градиент неон-центр → цвет роли); связь к «сияющему»
  монолитно светится статичной тенью drop-shadow (без бегущих импульсов).
- Прорастание новых линий из центра: stroke-dasharray/dashoffset синхронно с
  разлётом узла (кончик трекает аватарку); старые линии исчезают мгновенно.
- Ghost-слой: только аватарки (без линий), 1000мс — нет висящих «ошмётков».
- CSS-bloom разлёта на компоновщике (устойчив к троттлингу rAF; завершение по таймеру).
- Сияющие узлы: мягкая медленная пульсация 3.6с (многослойная box-shadow + SVG-ореол);
  тестовые фото-аватарки.
- Фильтры слоёв в лаборатории + фикс перехвата click сценой (stopPropagation на чипах);
  фейд скрываемых на месте (opacity 0 + scale 0.8, 300мс), фиксация без физики (ноль тряски).
- Бамп client.version → 1.2.137; обновлена документация фичи.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:17 +03:00
PixelandClaude Opus 4.8 e0f0726e68 Связи: интерактивная карта связей (force-directed graph)
Переработка экрана «Связи» в интерактивный нод-граф с премиальными переходами.

Движок (js/pages/network/force-graph.js):
- diffing-переходы: общие узлы перелетают, новые расцветают каскадом, исчезнувшие — Ghost-слой (800мс, на месте);
- мягкая радиальная пружина + отталкивание (органичная орбита), упругий влёт фокуса;
- динамическая вязкость на старте (трение 0.92→0.82, отталкивание ослаблено) — мягкий разлёт без тряски;
- жёсткая заморозка (kill-switch) при затухании — нет «треска», экономия батареи;
- линии — SVG <path> Безье (изогнутые нити), прорастание; жесты pan с инерцией;
- хард-лимит DOM-аватарок (остальное — SVG-точки).

Интеграция и UX:
- adapter.js: getUserConnectionsGraph → модель движка (сервер не трогаем, read-only);
- фильтры (Все/Семья/Друзья/Сияющие), контекстное меню (node-menu.js), нижний сниппет, профиль;
- прицел в центре, дыхание фокуса, свечение сияющих;
- лаборатория network-view/lab на мок-данных (networkGraphUsers) для тестов без бэкенда.

Документация: shine-UI/Dev_Docs/features/interactive-network-graph.md.
Бамп client.version 1.2.135 -> 1.2.136.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:16 +03:00
611 changed files with 66891 additions and 40446 deletions
+17
View File
@@ -76,10 +76,21 @@ shine-solana/shine/scripts/**/*.env
shine-solana/shine/scripts/**/TEMP_*.md
# Локальные артефакты и внешние материалы ESP32-подпроекта
ESP32/esp32-config-tool/
ESP32/**/.git/
ESP32/**/.idea/
ESP32-wallet/.idea/
ESP32/**/.arduino-build/
ESP32/**/official-demo/
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/**
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/**
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/**
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/**
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/lv_conf.h
ESP32/**/original-firmware/*.bin
ESP32/**/original-firmware/*.bin.sha256
ESP32/**/*.elf
@@ -93,3 +104,9 @@ ESP32/**/*.a
# Полные серверные бэкапы (тяжёлые архивы, не коммитим)
server-backup/archive/**
!server-backup/archive/.gitkeep
# Локальная дев-обвязка Claude (дев-сервер shine-UI, сессии, планы) — не коммитим
.claude/
# Рабочие бэкапы/превью-ассеты UI — не для репозитория
*.bak.png
shine-UI/assets/navbar_preview.png
Generated
+1 -3
View File
@@ -2,7 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo" vcs="Git" />
<mapping directory="$PROJECT_DIR$/tools/understand-anything-lab/upstream" vcs="Git" />
</component>
</project>
</project>
+36 -20
View File
@@ -24,12 +24,12 @@
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
## ESP32 UI сабсервера
## ESP32 UI homeserver
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
- Для нового ESP32 UI-прототипа сабсервера использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
- Для нового ESP32 UI-прототипа homeserver использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
## Solana-модуль
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
@@ -56,9 +56,12 @@
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
## Документация личных сообщений (DM)
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/README.md`.
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять `Dev_Docs/Personal_Messages/README.md`.
- Логика личных сообщений в коде должна всегда соответствовать `Dev_Docs/Personal_Messages/README.md`.
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`.
- Точный байтовый формат DM находится в `Dev_Docs/Personal_Messages/Формат_DM_v1.md`.
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять оба документа:
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`
- Логика личных сообщений в коде должна всегда соответствовать этим документам.
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
## Документация API сервера
@@ -68,10 +71,11 @@
- Без явного подтверждения пользователя формат серверного API не менять; допускается только приведение документации в соответствие уже существующему коду.
- Если добавляется новая операция `op`, нужно обновить общий список операций в `Dev_Docs/API/09_Operations_Index.md` или создать его, если файла ещё нет.
## Известная проблема (временная пометка)
- Мнения по связям пользователя (`known_person`, `shine_confirmed`, `shine_seen`) в UI могут отображаться нестабильно.
- Требуется отдельная доработка и финальная проверка end-to-end: запись мнения в блокчейн → обновление `connections_state` → ответ `GetUserConnectionsGraph` → отображение в UI.
- До фикса считать эту часть функционала незавершённой и обязательно перепроверять вручную после каждого деплоя.
## Документация Figma
- Актуальная документация по переносу экранов SHiNE в Figma и обратному переносу из Figma в код находится в `Dev_Docs/Figma/`.
- Точка входа: `Dev_Docs/Figma/README.md`.
- Подробный рабочий регламент: `Dev_Docs/Figma/TRANSFER_UI_SCREENS.md`.
- Для экранов регистрации, входа и других чувствительных UI-flow по умолчанию переносить экраны в Figma по одному, а не пачкой, если пользователь отдельно не подтвердил иной способ.
## Версионирование
- Единый файл версий проекта: `VERSION.properties` (в корне репозитория).
@@ -82,15 +86,25 @@
- Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется.
## Deploy
- Все документы и заметки по деплою хранить в папке `Dev_Docs/deploy/`.
- Базовый целевой хост для деплоя по умолчанию: `player@93.170.12.154` (`shineup.me`).
- Все документы и заметки по деплою хранить в папке `Deploy/`.
- Production-хост SHiNE: `player@shineup.me` (`178.208.64.62`).
- Второй production-хост SHiNE: `player@193.8.215.70` (`server2.shineup.me`).
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
- Для серверного деплоя использовать один gradle task: `./gradlew deployServer`.
- Для UI деплоя использовать один gradle task: `./gradlew deployUI`.
- Любые изменения и любой деплой на production `shineup.me` выполнять только после отдельного явного подтверждения пользователя.
- Если пользователь пишет просто `задеплой` без уточнения production/test, по умолчанию деплоить на `server2.shineup.me`.
- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`.
- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`.
- Production server deploy: `./gradlew deployServerProduction`.
- Production UI deploy: `./gradlew deployUIProduction`.
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
- Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`.
- Для продовой настройки тестового Arweave-кошелька JWK-файл нужно хранить только на сервере, например: `/home/player/SHiNE/secrets/test-free-avatar-wallet.json`.
- Для этой временной фичи на проде должны быть заданы параметры `test.freeAvatar.walletJwkPath` и `test.freeAvatar.walletAddress` через серверный override-конфиг/секреты на хосте.
- После изменения продовых значений `test.freeAvatar.*` нужно заново выполнить серверный деплой или перезапуск сервера, чтобы настройки были перечитаны приложением.
- При таких изменениях в git допускается коммитить только документацию и код чтения настроек, но не сам JWK, не содержимое секрета и не реальные приватные ключи.
## Логи звонков (установка соединения)
- Специальный поток диагностики установки звонков идёт через `CallDeliveryReport` (клиент → сервер).
@@ -122,17 +136,19 @@
- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
- В `Dev_Docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
## Будущие фичи
- Папка для задач, сознательно отложенных на будущее: `Dev_Docs/Future_Features/`.
- Точка входа по планам: `Dev_Docs/Future_Features/README.md`.
- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/`.
- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `Dev_Docs/Future_Features/README.md`, затем при необходимости конкретные файлы из горизонтов.
## Будущие фичи / TODO
- Папка для задач, сознательно отложенных на будущее: `TODO/`.
- Точка входа по планам: `TODO/README.md`.
- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/` и тематическим подпапкам.
- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `TODO/README.md`, затем при необходимости конкретные файлы из подпапок.
- Файлы из этой папки не считать активными задачами и не начинать реализацию без явной просьбы пользователя.
- Если часть кода временно отключена или закомментирована, в файле будущей фичи подробно описывать:
- Старую папку `Dev_Docs/Future_Features/` считать выведенной из использования и больше не использовать для новых записей.
- Если часть кода временно отключена или закомментирована, либо удалена как временная заглушка, в TODO-файле подробно описывать:
- какие файлы и участки отключены;
- что осталось в коде как заготовка;
- какие документы нужно обновить при возврате;
- с какого сценария продолжать разработку.
- с какого сценария продолжать разработку;
- из какого коммита брать последнюю полную реализацию.
## Коммуникация по новым задачам (обязательно)
- При получении нового задания сначала кратко пересказать задачу своими словами.
-255
View File
@@ -1,255 +0,0 @@
# DAO_запуск
Рабочий документ по тому, что ещё нужно сделать для первого запуска DAO-сценария SHiNE.
Логика документа:
- `этап1` — то, без чего нельзя считать сценарий первого запуска собранным даже в тестовом виде;
- `этап2` — то, что полезно и, вероятно, потребуется дальше, но это можно делать после старта `этап1` или параллельно без блокировки первого результата.
Базовая среда первого прохода:
- сеть `Solana devnet`;
- модель синхронизации: `server-to-server`;
- `Solana + Arweave` используются как якорь и архив;
- DAO понимается как стандартный governance/smart-contract контур, который управляет отдельными программами SHiNE, приносящими деньги.
## Краткий вывод
Для первого запуска DAO в тестовом виде текущего списка в целом хватает, но только если понимать запуск как:
- можно развернуть и проверить базовый DAO-контур;
- можно зарегистрировать пользователей и ключевые сущности;
- можно провести тестовую покупку билета через smart contract;
- можно завести тестовый денежный поток в программы, управляемые DAO;
- можно проверить опорную межсерверную синхронизацию и фиксацию состояния в архивный слой.
Если же под "запуском" понимать уже полностью устойчивую production-схему с ротацией ключей, восстановлением любого сервера из архива, железными устройствами подписи и полным циклом администрирования, то текущий список нужно будет ещё расширять.
## Этап1
Цель этапа: собрать минимально жизнеспособный DAO-сценарий в `devnet`, который можно пройти руками от регистрации до базовой экономики и проверки архитектуры.
### 1. Переписать и стабилизировать регистрацию пользователей без Anchor
Что сделать:
- довести `shine_users` в чистом Rust/Solana SDK до рабочего и проверенного состояния;
- убедиться, что `shine_login_guard` и связанный сценарий регистрации совместимы с новым ABI;
- проверить создание и чтение `user_pda`;
- проверить update пользовательской записи и связанные экономические параметры;
- синхронизировать сервер, UI и lazy-import с новым форматом и seed'ами.
Почему это в `этап1`:
- без стабильной пользовательской регистрации дальше нельзя строить ни DAO-сценарий, ни привязку устройств, ни платёжные сценарии.
### 2. Проверить полный сценарий регистрации и базовой Solana-интеграции
Что сделать:
- руками прогнать регистрацию нового пользователя;
- руками прогнать создание и update server PDA там, где это требуется текущему сценарию;
- убедиться, что сервер читает новые PDA без anchor-зависимостей и без старых discriminator'ов;
- зафиксировать, какие именно части сценария уже подтверждены руками, а какие ещё нет.
Почему это в `этап1`:
- сейчас в проекте уже есть признаки перехода на pure Rust, но без ручной проверки это нельзя считать завершённым.
### 3. Создать стандартный DAO smart contract / governance-контур
Что сделать:
- определить и реализовать стандартный DAO-контур, который будет управлять программами SHiNE;
- зафиксировать, какие права сразу передаются DAO, а какие временно остаются на отдельных ключах;
- подготовить тестовую DAO-структуру в `devnet`.
Минимум для первого запуска:
- DAO существует как управляемая сущность;
- DAO может владеть или контролировать ключевые права управления денежными программами;
- есть понятный путь, как DAO влияет на доходные программы SHiNE.
Почему это в `этап1`:
- без этого "DAO-запуск" будет только запуском отдельных Solana-программ, но не запуском управляемой DAO-системы.
### 4. Доработать смарт-контракт выплат с третьей очередью
Что сделать:
- добавить в `shine_payments` третью очередь, о которой уже принято решение;
- проверить совместимость с текущей моделью тикетов, выплат и DAO-управления;
- убедиться, что логика очередей соответствует ожидаемой экономике проекта.
Почему это в `этап1`:
- по текущей постановке это нужно именно для сценария регистрации DAO и дальнейшей экономики.
### 5. Сделать UI для покупки билетов и просмотра очереди
Что сделать:
- добавить UI-сценарий покупки билетов через smart contract;
- показать пользователю, сколько перед ним человек в очереди;
- убедиться, что UI отражает актуальное состояние контрактной логики, а не локальные предположения.
Почему это в `этап1`:
- покупка билетов у тебя обозначена как часть DAO-сценария, а не как побочная функция;
- без UI можно тестировать контракт вручную, но нельзя считать сценарий запуска достаточно собранным для нормальной проверки.
### 6. Реализовать базовую синхронизацию серверов
Что сделать:
- сделать обмен состоянием между серверами по модели `server-to-server`;
- определить минимальный набор данных, который обязан синхронизироваться;
- предусмотреть фиксацию синхронизированного состояния в `Arweave`, а `Solana` использовать как якорь и ссылочный слой;
- описать, какой сервер считается источником истины в спорных случаях или как решается конфликт.
Почему это в `этап1`:
- без межсерверной синхронизации трудно обосновать архитектуру сети как воспроизводимую и переносимую;
- это напрямую связано с идеей, что любой сможет поднять свой сервер.
### 7. Подготовить базовый сценарий архивирования и восстановления
Что сделать:
- описать и частично реализовать схему: серверы синхронизируются между собой, архив состояния уходит в `Arweave`, ссылка/якорь фиксируется через `Solana`;
- определить минимальный сценарий восстановления блоков или состояния из архивного слоя;
- подтвердить, что новый сервер может получить достаточно данных для старта.
Почему это в `этап1`:
- это один из ключевых признаков независимой и воспроизводимой DAO-инфраструктуры.
## Этап2
Цель этапа: усилить безопасность, автономность и удобство системы после того, как минимальный DAO-сценарий уже запустился и проверен в `devnet`.
### 1. Смена ключей цифровой подписи
Что сделать:
- продумать и реализовать смену `root key`, `device key`, `blockchain key`;
- описать ограничения, кто и в каком сценарии может менять каждый тип ключа;
- продумать, как не потерять доступ и как обновлять доверие к новым ключам.
Почему это в `этап2`:
- для production это очень важно;
- для первого тестового запуска можно временно использовать фиксированный набор ключей.
### 2. Полная повторная перепроверка всех сценариев
Что сделать:
- повторно прогнать регистрацию, DAO, выплаты, билеты, синхронизацию и архивирование после стабилизации `этап1`;
- оформить итоговый чек-лист ручной проверки;
- отдельно проверить пограничные сценарии и восстановление после ошибок.
Почему это в `этап2`:
- это обязательный шаг перед переходом от "собрали" к "доверяем".
### 3. Устройство на ESP32 как сабсервер с ключами
Что сделать:
- дописать прошивку, чтобы устройство могло выступать сабсервером с ключами;
- дать ему возможность регистрироваться и подключаться к серверу;
- определить, какие операции устройство подписывает и где хранит ключевой материал.
Почему это в `этап2`:
- это очень сильное развитие архитектуры, но оно не должно блокировать первый DAO-запуск.
### 4. Логин и подпись через коробочки / устройства
Что сделать:
- реализовать сценарий входа через устройство или хотя бы сценарий подписи сообщений и ключей через устройство;
- определить, как это встраивается в регистрацию DAO и подтверждение действий;
- проверить, можно ли через это безопасно регистрировать DAO или подписывать критичные команды.
Почему это в `этап2`:
- это следующий уровень безопасности и UX, но не минимальный блокер первого старта.
### 5. Создание тестового DAO с использованием устройств подписи
Что сделать:
- после готовности устройств собрать тестовый DAO-сценарий уже с аппаратным участием;
- проверить, где устройство достаточно, а где всё ещё нужен обычный кошелёк или управляющий ключ.
Почему это в `этап2`:
- это проверка усиленной модели, а не базового старта.
### 6. Расписание синхронизации серверов
Что сделать:
- определить периодичность и правила фоновой синхронизации;
- продумать ручной и автоматический режим;
- решить, как часто публиковать архивные снимки и якоря.
Почему это в `этап2`:
- сначала важнее добиться самой работающей синхронизации, а потом уже делать её регулярной и автономной.
### 7. Полное восстановление блоков из Solana/Arweave
Что сделать:
- довести процедуру восстановления до сценария "любой может поднять свой сервер";
- определить минимальный bootstrap-набор;
- проверить восстановление на чистом окружении.
Почему это в `этап2`:
- для концепции сети это критично, но как полноценная задача обычно идёт после появления базового архива и первичной синхронизации.
## Что блокирует первый запуск сильнее всего
Если расставить приоритет внутри `этап1`, то самый жёсткий порядок сейчас выглядит так:
1. pure Rust регистрация пользователей и ручная проверка сценария;
2. DAO/gov-контур и его права управления;
3. доработка выплат с третьей очередью;
4. покупка билетов через smart contract и UI-проверка очереди;
5. межсерверная синхронизация;
6. архивирование в `Arweave` с якорем в `Solana`;
7. минимальное восстановление состояния новым сервером.
## Что уже частично похоже на готовое
По текущим документам и следам в проекте уже видно, что:
- переход `shine_users` и `shine_login_guard` на pure Rust уже начат и в значительной степени сделан;
- архитектура DAO, `shine_users` и `shine_payments` уже описана;
- часть Solana-структуры и PDA-форматов уже формализована;
- тема ESP32 уже отдельно присутствует в проекте как направление.
Это хорошо, потому что документ получается не "с нуля", а как сборка того, что уже назрело в коде и планах.
## Вопросы, которые всё ещё стоит уточнить
1. Какой именно стандарт DAO планируется использовать в первом проходе: готовый governance-стек Solana или собственная минимальная обвязка вокруг управляющих кошельков?
2. Третья очередь в `shine_payments` уже точно определена по смыслу, или пока есть только решение "она нужна", но без финальной экономики?
3. Что именно считается единицей синхронизации между серверами: блоки SHiNE, агрегированные снапшоты, PDA-состояния, или смесь этих вариантов?
4. Нужен ли для `этап1` уже полноценный автоматический recovery нового сервера, или достаточно доказать это в полу-ручном сценарии?
5. Покупка билетов должна в первом проходе работать только через web/UI, или также нужен отдельный сценарий из серверного UI или скриптов?
## Рекомендуемый следующий практический шаг
Если идти без распыления, то следующим рабочим фокусом стоит считать:
1. закрыть ручную проверку pure Rust регистрации;
2. после этого формализовать минимальный DAO-контур;
3. затем переходить к третьей очереди выплат и к UI покупки билетов;
4. после этого делать синхронизацию, архив и восстановление.
-92
View File
@@ -1,92 +0,0 @@
# DEBUG: тестирование сетевого соединения между двумя клиентами
Документ описывает временный debug-контур для проверки WebRTC соединения между двумя активными WS-сессиями.
## 1) Подготовка
0. Убедись, что в `application.properties` включен параметр:
`debug.tempApi.enabled=true`
1. Создай файл `.debug-token` в корне проекта на основе `debug-token.example`.
2. В `.debug-token` должна быть одна строка: секретный токен.
3. Перезапусти сервер.
## 2) API debug
Базовый заголовок для всех запросов:
```bash
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>"
```
### 2.1 Получить список живых клиентов
```bash
curl -s \
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
http://localhost:7070/debug/ws/clients | jq
```
Ответ содержит `sessionId`, `login`, `ip`, `userAgent`, и клиентскую информацию.
### 2.2 Запустить debug-соединение между двумя сессиями
```bash
curl -s -X POST \
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"initiatorSessionId": "SESSION_ID_A",
"responderSessionId": "SESSION_ID_B",
"clearDebugLog": false
}' \
http://localhost:7070/debug/ws/connect | jq
```
В ответе придёт `runId`. Его используй для фильтра логов.
### 2.3 Читать последние N debug-логов
```bash
curl -s \
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
"http://localhost:7070/debug/ws/logs?limit=200&runId=<RUN_ID>" | jq
```
## 3) Операционный сценарий “Codex + пользователь”
1. Codex поднимает сервер и сообщает пользователю ссылку на UI.
2. Codex пишет пользователю: **«Запусти двух клиентов и скажи “продолжай”»**.
3. Пользователь запускает два клиента (лучше под разными логинами).
4. Пользователь пишет: **«продолжай»**.
5. Codex:
- вызывает `/debug/ws/clients`,
- выбирает 2 сессии,
- вызывает `/debug/ws/connect`,
- получает `runId`,
- читает `/debug/ws/logs?runId=...` и сообщает прогресс.
6. Если соединение не удалось:
- Codex сообщает ошибки по логам,
- при необходимости просит перезапустить 2 клиента,
- повторяет запуск debug-run.
## 4) Какие сообщения считать успехом
- `peer_connection_connected`
- `debug_connection_success`
- `signal_sent_200/210/220` без ошибок
## 5) Что говорить пользователю в ходе прогона (через «колонку»/чат)
Рекомендуемые фразы:
- «Сервер запущен. Запусти двух клиентов и напиши “продолжай”.»
- «Вижу 2 активные сессии, запускаю тест соединения.»
- «Тест запущен, runId=... Сейчас проверяю логи.»
- «Соединение установлено / не установлено. Ниже причины и следующий шаг.»
## 6) Ограничения
- Механизм временный, не для production-эксплуатации.
- Доступ к debug API имеет любой, кто знает токен.
- Рекомендуется тестить между разными логинами.
@@ -1,62 +0,0 @@
0. ПЕРЕДЕЛАТЬ ВСЁ НА НОВЫЙ ФОРМАТ!!
ВЫНЕСТИ ЭТИ ТРИ ВЕЩИ В ОБЩИЙ ПАРСЕР
* [2] type - тип соощения
* [2] Sиbtype - субтип сообщения
* [2] version - версия формата соощения
А ОСТАЛЬНОЕ В РЕАЛИЗАЦИЮ
ПЕРЕДЕЛАЕМ БД
1. СДЕЛАЕМ ЛИНИЮ ТОЛЬКО ДЛЯ ТЕХ ТИПОВ КОМУ НАДО (ЛАЙКАМ И ОТВЕТАМ НЕ НАДО)
(НОМЕР СООБЩЕНИЯ В ЛИНИИ ХРАНИТЬ В БЛОКАХ ВРОДЕ И НЕ НАДО ТЕМ БОЛЕЕ ЕГО ПОТОМ ПЕРЕПРОВЕРЯТЬ ВСЁ РАВНО)
А МОЖЕТ И НАДО ТК КАК ПО ОДНОМУ БЛОКУ ( ИЛИ ЧАСТИ БЛОКОВ ПОНЯТЬ КАКАЯ ЭТО ЧАСТЬ ПЕРЕПИСКИ - ВЕДЬ ГЛОБАЛ НОМЕР ВООБЩЕ НЕ ПОКАЗАТЕЛЬ)
В БД ПОМЕЧАТЬ ЧТО БЛОК ИЗ ЭТОЙ ЛИНИИ (ДЛЯ БЫСТРОГО ПОИСКА)
А УНИКАЛЬНЫЙ НОМЕР ЛИНИИ ЭТО ПО СУТИ НОМЕР СООБЩЕНИЯ СОЗДАВШЕГО ЛИНИЮ КАНАЛ (НУ И ФОРМАТ СООБЩЕНИЯ НАЧАЛА ЛИНИИ - КАНАЛА)
3. СООТВЕТСТВЕННО удалить НАПИСАТЬ/ПЕРОВЕРИТЬ НОРМАЛЬНЫЙ SubscriptionsDAO - ТК СТАРЫЙ РАБОТАЛ НО НА ДРУГОМ ФОРМАТЕ И ТИПО КРИВО
и дальше:
ЗДЕЛАТЬ ТРИ ЗАПРОСА:
СПИСОК КАНАЛОВ НА КОГО ПОДПИСАН И ПО СКОЛЬКО СООБЩЕНИЙ И ПОСЛДНИЙ ТЕКСТ
ДОДЕЛАТЬ И СВЯЗ ПОДПИСАН УЖЕ НЕ ТОЛЬКО НА ЧЕЛА НО И НА КАНАЛ. (И ПОЛУЧАЕТСЯ ЕСТЬ ОБЩИЙ КАНАЛЛ ПОСТОВ (НО НЕКОТОРЫЕ ПОСТЫ В НИКУДА-
А НЕКОТОРЫЕ ПОСТЫ ОБЪЯВЛЕНИЕ КАНАЛА
СПИСОК СООБЩЕНИЙ В КАНАЛЕ
ОПСИСАНИЕ ОДНОГО СОООБЩЕНИЯ (С ИСТОРИЕЙ ДО НАЧАЛА ВЕТКИ И СО ВСЕМИ ОТВЕТАМИ НА НЕГО)
(НУ И В БУДУЩЕМ четвёртый ИСТОРИЮ сообщения ПО ЕДИТУ)
И ПОМЯТКА
ВСЕГДА СЧИТАЕМ ПО ПОСЛЕДНЕМУ БЛОКЧЕЙНУ ДОСТУПНОМУ ПОЛЬЗОВАТЕЛЮ
ХОТЯ ССЫЛКА ПО НОМЕРУ БЛОКЧЕЙНА КУДА ДОБАВИЛИ
ЛАЙКИ И ОТВЕТЫ ПИШЕМ НА НОМЕР СООБЩЕНИЯ ЕДИТА
(СЧИТАЕМ ТРИГЕРОМ И НА ОРИГИНАЛЬНЫЙ СУМАРНОЕ И ОТДЕЛЬНО НА НЕГО, И НА КАЖДЫЙ ЕДИТ ОТДЕЛЬНО)
ОТВЕТЫ ПОКАЗЫВАЕМ ВСЕ ВРАЗ
@@ -1,10 +0,0 @@
Сделать возможность убрать свой лайк. (пока не надо а сложность что надо больше проверок) - хотя можно и без проверки, просто за двойной лайк или за снятие двойное лайка. Будет двойное проникновение :)) тому кто изменил код клиента и убрал проверку на клиенте - и блокчейн заблокируется и всё.
поэтому просто на каждую реакцию добавиться убрать эту ракцию .
- это просто
сделатьпотом что бы в солану_юзерс хранилось имя текущего блокчейна пользователя. Что бы потом можно было грузить именно актуальный ТО ЕСТЬ потом можно будет менять блокченый!
сделать сессион пасворд тоже ключём подписи устройства!!
-22
View File
@@ -1,22 +0,0 @@
Перечень библиотек и их краткое описание
shine-server-log
Статический “сиренный” метод для максимально заметного критического лога администратору
shine-server-config
Минимальный конфиг-лоадер, который один раз читает application.properties и даёт доступ к параметрам.
shine-server-geo
Утилиты, которые вытаскивают IP/язык/UA из Jetty WebSocket и (опционально) резолвят гео по IP с кэшем в БД.
shine-server-crypto
Базовые крипто-утилиты для SHA-256 и Ed25519 (BouncyCastle) + проверка подписи/хэша для .bch сущностей и маленький self-test.
shine-server-bd
Библиотека реалезующая всю работу с БД:
shine-server-blockchain
Библиотека, которая задаёт единый бинарный формат блоков (RAW+signature+hash), парсит/валидирует “тело” блока по type/version, и проверяет целостность/подпись цепочки через SHA-256 + Ed25519 с привязкой к login и предыдущим хэшам.
shine-server-protocol
Библиотека JSON-протокол поверх WebSocket для взаимодействия с клиентами.
-209
View File
@@ -1,209 +0,0 @@
SHiNE — структура БД (актуальная версия)
Перечень таблиц и назначение
solana_users
Справочник пользователей: логин + ключ устройства + (опционально) Solana-ключ.
Базовая таблица, используется как FK почти везде.
active_sessions
Активные сессии авторизации/работы клиента: секреты, тайминги, WebPush-данные, IP и информация о клиенте.
users_params
Хранилище актуальных параметров пользователя.
Для каждой пары (login, param) хранится только самая новая версия по time_ms.
ip_geo_cache
Кеш геолокации по IP для снижения нагрузки на внешние сервисы.
blockchain_state
Агрегированное состояние блокчейна по blockchain_name:
лимиты, текущий размер, последний глобальный блок и состояние линий 0..7.
blocks
Журнал всех блоков и сообщений.
Содержит историю событий: тексты, реакции, ответы, связи.
PRIMARY KEY намеренно отсутствует.
connections_state ⭐
Актуальное состояние связей между пользователями
(друг / контакт / подписка).
Обновляется автоматически на основе событий из blocks.
message_stats ⭐
Агрегированные счётчики лайков и ответов на конкретные сообщения.
Поддерживается триггерами из blocks.
Таблицы подробно
solana_users
login — TEXT PK — уникальный логин пользователя
device_key — TEXT NOT NULL — публичный ключ устройства (Base64(32) / HEX(64))
solana_key — TEXT NULL — публичный ключ Solana-аккаунта
active_sessions
session_id — TEXT PK — идентификатор сессии
login — TEXT NOT NULL, FK → solana_users(login)
session_pwd — TEXT NOT NULL — секрет сессии
storage_pwd — TEXT NOT NULL — секрет storage
session_created_at_ms — INTEGER NOT NULL
last_authirificated_at_ms — INTEGER NOT NULL
push_endpoint — TEXT NULL
push_p256dh_key — TEXT NULL
push_auth_key — TEXT NULL
client_ip — TEXT NULL
client_info_from_client — TEXT NULL
client_info_from_request — TEXT NULL
user_language — TEXT NULL
users_params
login — TEXT NOT NULL, FK → solana_users(login)
param — TEXT NOT NULL
time_ms — INTEGER NOT NULL
value — TEXT NOT NULL
device_key — TEXT NULL
signature — TEXT NULL
Ограничение:
UNIQUE(login, param)
Логика:
обновление принимается только если excluded.time_ms > users_params.time_ms
ip_geo_cache
ip — TEXT PK
geo — TEXT NULL
updated_at_ms — INTEGER NOT NULL
blockchain_state
blockchain_name — TEXT PK
login — TEXT NOT NULL, FK → solana_users(login)
blockchain_key — TEXT NOT NULL
size_limit — INTEGER NOT NULL
file_size_bytes — INTEGER NOT NULL
last_global_number — INTEGER NOT NULL (-1 = genesis)
last_global_hash — TEXT NOT NULL
updated_at_ms — INTEGER NOT NULL
Линии 0..7:
для каждой линии:
lineX_last_number
lineX_last_hash
blocks
login — TEXT NOT NULL
bch_name — TEXT NOT NULL
block_global_number — INTEGER NOT NULL
block_global_pre_hash — TEXT NOT NULL
block_line_index — INTEGER NOT NULL
block_line_number — INTEGER NOT NULL
block_line_pre_hash — TEXT NOT NULL
msg_type — INTEGER NOT NULL
msg_sub_type — INTEGER NOT NULL
block_bytes — BLOB NULL
Ссылка на другой блок (nullable):
to_login
to_bch_name
to_block_global_number
to_block_hash
connections_state ⭐
Текущее агрегированное состояние связей.
login — TEXT NOT NULL
rel_type — INTEGER NOT NULL
10 = FRIEND
20 = CONTACT
30 = FOLLOW
to_login — TEXT NOT NULL
to_bch_name — TEXT NOT NULL
to_block_global_number — INTEGER NULL
to_block_hash — TEXT NULL
Ограничение:
UNIQUE(login, rel_type, to_login)
message_stats ⭐
Счётчики активности по целевому сообщению.
to_login — TEXT NOT NULL
to_bch_name — TEXT NOT NULL
to_block_global_number — INTEGER NOT NULL
to_block_hash — TEXT NOT NULL
likes_count — INTEGER NOT NULL DEFAULT 0
replies_count — INTEGER NOT NULL DEFAULT 0
UNIQUE:
(to_login, to_bch_name, to_block_global_number, to_block_hash)
Триггеры БД (полная логика)
3.1 Связи пользователей
trg_blocks_connection_state_ai
AFTER INSERT ON blocks
Условие:
msg_type = 3 (connection)
Добавление / обновление связи
msg_sub_type IN (10,20,30)
выполняется UPSERT в connections_state
Удаление связи
msg_sub_type IN (11,21,31)
удаляется соответствующая связь:
11 → 10
21 → 20
31 → 30
Итог:
blocks — журнал событий
connections_state — всегда актуальное состояние
3.2 Подсчёт лайков ⭐
trg_blocks_message_stats_like_ai
AFTER INSERT ON blocks
Условие:
msg_type = 2 (reaction)
msg_sub_type = 1 (like)
Действие:
определяется цель по to_bch_name, to_block_global_number, to_block_hash
to_login вычисляется как
substr(to_bch_name, 1, length(to_bch_name) - 3)
выполняется UPSERT в message_stats
likes_count += 1
3.3 Подсчёт ответов ⭐
trg_blocks_message_stats_reply_ai
AFTER INSERT ON blocks
Условие:
msg_type = 1 (text)
msg_sub_type = 2 (reply)
Действие:
цель определяется аналогично лайкам
выполняется UPSERT в message_stats
replies_count += 1
Индексы (смысл)
idx_solana_users_login — поиск пользователя
idx_active_sessions_login — сессии пользователя
idx_users_params_login — параметры пользователя
idx_ip_geo_cache_updated_at — чистка кеша
idx_blockchain_state_login — блокчейны пользователя
idx_blockchain_state_updated_at — обслуживание
idx_blocks_chain_global — чтение цепочки
idx_blocks_to_target — реакции / ответы
idx_message_stats_target — быстрый доступ к счётчикам
Итоговая модель мышления
blocks — неизменяемый журнал событий
connections_state — проекция связей
message_stats — проекция активности
всё вычисляется детерминированно через триггеры
@@ -1,75 +0,0 @@
# Протокол звонков (MVP)
Версия: browser-to-browser, runtime-only signaling.
## Цели
- Технические сообщения звонка не сохраняются в БД direct_messages.
- Первый INVITE рассылается всем активным сессиям получателя и дублируется web push.
- Последующие сигналы идут только в конкретную sessionId и не дублируются в push.
## Операции API
### 1) CallInviteBroadcast
Отправляет общий вызов пользователю.
Запрос payload:
- `toLogin: string`
- `callId: string`
- `type: 100` (INVITE)
Поведение сервера:
- Рассылает `IncomingCallInvite` во все активные WS-сессии `toLogin`.
- В payload события передаёт:
- `fromLogin`
- `fromSessionId` (session инициатора)
- `toLogin`
- `callId`
- `type=100`
- `timeMs`
- Отправляет web push уведомление о входящем вызове.
Ответ payload:
- `callId`
- `deliveredWsSessions`
- `deliveredFcmSessions`
### 2) CallSignalToSession
Отправляет технический сигнал в конкретную сессию.
Запрос payload:
- `toLogin: string`
- `targetSessionId: string`
- `callId: string`
- `type: int`
- `data: string` (для SDP/ICE/служебных строк)
Поведение сервера:
- Ищет только `targetSessionId`.
- Проверяет, что сессия принадлежит `toLogin`.
- Отправляет `IncomingCallSignal` только в эту сессию.
- В БД ничего не сохраняет.
- Push не отправляет.
Ответ payload:
- `delivered: boolean`
## Коды type
- `100` INVITE
- `110` RINGING
- `120` ACCEPT
- `130` DECLINE_BUSY
- `140` TIMEOUT
- `150` HANGUP
- `200` OFFER
- `210` ANSWER
- `220` ICE
## Правила UI/логики
- Если уже есть активный звонок и пришел новый INVITE -> автоответ `DECLINE_BUSY` без UI.
- После ACCEPT `callId` остаётся во всех OFFER/ANSWER/ICE сообщениях до конца звонка.
- При параллельных звонках A<->B допускается детерминированное правило, кто создаёт OFFER.
## Тайминги MVP
- Ожидание подтверждения/реакции после INVITE: до 5с (у инициатора).
- Ожидание принятия у входящего звонка: 20с.
- Общий лимит ожидания до соединения: 22с.
@@ -1,9 +0,0 @@
Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.
---
Потом в сервак дописать синхронизацию серверов.
-53
View File
@@ -1,53 +0,0 @@
# SHiNE Deployment Servers Inventory
## Scope
This folder contains all deployment-related notes and server records for SHiNE.
## Legacy Production Server
- Name: `VPS-02` (legacy)
- Access: `root@194.87.0.247`
- Current role: old production server
- Confirmed services:
- `coturn` is installed and active (`systemd: active/running`)
- `caddy` is installed (reported by project context; verify version on host if needed)
- TURN configuration observed on host:
- `listening-port=3478`
- `external-ip=194.87.0.247`
- `relay-ip=194.87.0.247`
- auth mode: `use-auth-secret` + `static-auth-secret`
- SHiNE deployment note:
- This host is used as current/legacy runtime for SHiNE.
- Gradle-based deployment is used in this project (see repository deploy tasks and scripts).
## Target Production Server (Migration)
- Name: `VPS-05` (new)
- Access: `root@45.136.124.227`
- Planned role: new primary production server for gradual migration
- Baseline setup done:
- `ripgrep` installed
- user `player` created
- user `player` added to `sudo` group
- deployment directory created: `/home/player/SHiNE`
- Rule:
- All SHiNE-related runtime files and deployments on VPS-05 should be placed under `/home/player/SHiNE`.
## Additional TURN Node
- Name: `promo-node-93`
- Access: `ubuntu@93.170.12.154` (and `player` user for SHiNE operations)
- Role: additional TURN node for SHiNE calls
- TURN setup:
- `coturn` installed and active
- `listening-port=3478`
- `tls-listening-port=5349`
- `use-auth-secret` + shared `static-auth-secret`
- relay UDP port range: `49152-50152`
- Runtime files:
- `/etc/turnserver.conf`
- `/home/player/SHiNE/coturn/turnserver.conf`
- Cleanup done:
- Disabled old reverse SSH tunnel (`reverse-ssh.service`) that exposed `0.0.0.0:1200 -> localhost:22` to `194.87.0.247`.
## Next Migration Steps (recommended)
1. Install and configure runtime dependencies (JDK, Caddy, DB, TURN if required).
2. Mirror SHiNE deployment process from VPS-02 using existing Gradle deployment flow.
3. Move traffic gradually and validate logs/metrics before final cutover.
+93
View File
@@ -0,0 +1,93 @@
# Production-серверы SHiNE
## Короткий ответ
По текущим данным репозитория у SHiNE описаны **два production-контура**:
- `player@shineup.me`
- домен `shineup.me`
- IP `178.208.64.62`
и
- `player@193.8.215.70`
- домен `server2.shineup.me`
- IP `193.8.215.70`
## 1. Основной production-хост
- SSH: `player@shineup.me`
- домен: `shineup.me`
- IP: `178.208.64.62`
- пользователь: `player`
- базовый путь: `/home/player`
Основные каталоги:
- проект SHiNE: `/home/player/SHiNE`
- серверный jar: `/home/player/SHiNE/shine-server/shine-server.jar`
- UI: `/home/player/SHiNE/shine-ui`
- данные: `/home/player/SHiNE/shine-server/data/`
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
Сервисы:
- `shine-server.service`
- `caddy.service`
Caddy:
- активный конфиг: `/etc/caddy/Caddyfile`
- UI root: `/home/player/SHiNE/shine-ui`
- `/ws` проксируется на `127.0.0.1:7070`
Deploy:
- `./gradlew deployServerProduction`
- `./gradlew deployUIProduction`
Правило:
- любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
## 2. Второй production-сервер
- SSH: `player@193.8.215.70`
- домен: `server2.shineup.me`
- IP: `193.8.215.70`
- пользователь: `player`
- базовый путь: `/home/player`
Роль:
- второй production-контур SHiNE;
- использовать как production-сервер, несмотря на исторические имена deploy-задач
`deployServerTest2` / `deployUITest2`.
Основные каталоги:
- проект SHiNE: `/home/player/SHiNE`
- серверный jar: `/home/player/SHiNE/shine-server/shine-server.jar`
- UI: `/home/player/SHiNE/shine-ui`
- данные: `/home/player/SHiNE/shine-server/data/`
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
## 3. Связанные публичные production-публикации на том же хосте
На этом же production-хосте есть отдельная публикация для `shine_payments`:
- каталог: `/home/player/sites/test-solana-tickets.shineup.me`
- домены:
- `https://test-solana-tickets.shineup.me`
- `https://test-solana-tickets.shiningpeople.ru`
Это не второй production-хост SHiNE, а отдельный сайт на том же сервере.
## 4. Какие серверы не считать production
Не production:
- `t1.shineup.me`
- `t2.shineup.me`
- `t3.shineup.me`
- `t4.shineup.me`
+35
View File
@@ -0,0 +1,35 @@
# Deploy
Подробности о том, где что задеплоено в SHiNE, нужно искать в папке `Deploy/`.
Эта папка служит краткой картой окружений:
- [TEST_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/TEST_SERVERS.md) — тестовые стенды;
- [PRODUCTION_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/PRODUCTION_SERVERS.md) — production-контур и связанные публичные публикации.
Ниже краткая сводка.
## Основные публичные контуры
- Production SHiNE:
- `player@shineup.me`
- домен `shineup.me`
- IP `178.208.64.62`
- Второй production SHiNE:
- `player@193.8.215.70`
- домен `server2.shineup.me`
- IP `193.8.215.70`
## Отдельный quad-devnet стенд
На отдельном VPS `178.208.90.249` подняты 4 независимых test/devnet-инстанса:
- `t1.shineup.me`
- `t2.shineup.me`
- `t3.shineup.me`
- `t4.shineup.me`
## Важно
- Production-контура SHiNE сейчас два: `shineup.me` и `server2.shineup.me`.
- `t1..t4.shineup.me` — это отдельные тестовые/devnet-контуры, не production.
- Любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
+124
View File
@@ -0,0 +1,124 @@
# Тестовые серверы SHiNE
Этот файл описывает тестовые стенды, которые сейчас фигурируют в проекте.
## 1. Исторический `test2`, теперь второй production-сервер
- SSH: `player@193.8.215.70`
- Домен: `server2.shineup.me`
- IP: `193.8.215.70`
- Назначение: второй production-контур SHiNE
Структура:
- каталог SHiNE: `/home/player/SHiNE`
- сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
- UI: `/home/player/SHiNE/shine-ui`
- данные: `/home/player/SHiNE/shine-server/data/`
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
Сервисы:
- `shine-server.service`
- `caddy.service`
Deploy:
- `./gradlew deployServer`
- `./gradlew deployServerTest2`
- `./gradlew deployUI`
- `./gradlew deployUITest2`
Примечания:
- этот хост больше не считать test-контуром;
- исторические имена deploy-задач `deployServerTest2` / `deployUITest2` сохранены, но сам хост считать production;
- задача `deployUITest2` по умолчанию выкладывает UI на `server2.shineup.me`, а не на `t2.shineup.me`;
- при описании окружений перечислять его как второй production-сервер.
## 2. Отдельный quad-devnet стенд `t1..t4`
- VPS: `178.208.90.249`
- пользователь: `player`
- назначение: 4 независимых SHiNE-инстанса на Solana `devnet`
Домены и логины:
- `server_t1` -> `https://t1.shineup.me`
- `server_t2` -> `https://t2.shineup.me`
- `server_t3` -> `https://t3.shineup.me`
- `server_t4` -> `https://t4.shineup.me`
Каталоги:
- `/home/player/t1/server`
- `/home/player/t1/UI`
- `/home/player/t2/server`
- `/home/player/t2/UI`
- `/home/player/t3/server`
- `/home/player/t3/UI`
- `/home/player/t4/server`
- `/home/player/t4/UI`
Подробная памятка на самом VPS:
- `/home/player/Agents.md`
Порты и systemd:
- `t1` -> `7101` -> `shine-t1.service`
- `t2` -> `7102` -> `shine-t2.service`
- `t3` -> `7103` -> `shine-t3.service`
- `t4` -> `7104` -> `shine-t4.service`
Что важно по конфигу каждого инстанса:
- отдельный `/home/player/tX/server/application.properties`
- `server.port=710X`
- `server.SHiNE.login=server_tX`
- `db.path=data/shine.sqlite`
- `solana.cluster=devnet`
- `solana.rpcUrl=https://api.devnet.solana.com`
- `server.ui.indexPath=/home/player/tX/UI/index.html`
- `server.info.url=https://tX.shineup.me`
UI каждого инстанса:
- живёт в отдельной копии `shine-UI`;
- использует свой `js/deploy-config.js`;
- по умолчанию смотрит именно на свой `tX.shineup.me`.
Caddy на стенде:
- конфиг: `/etc/caddy/Caddyfile`
- статика: `/home/player/tX/UI`
- `/ws` проксируется на `127.0.0.1:710X`
Operational-нюанс:
- при одновременных рестартах возможны `HTTP 429` от `api.devnet.solana.com`;
- поэтому сервисы `shine-t1..shine-t4` лучше перезапускать по одному, с паузой.
## 3. Что проверять первым делом
Для любого test-контура полезны такие быстрые проверки:
```bash
curl -I https://server2.shineup.me
curl -I https://t1.shineup.me
curl -I https://t2.shineup.me
curl -I https://t3.shineup.me
curl -I https://t4.shineup.me
```
Для quad-devnet VPS:
```bash
sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
```
Для второго production-контура:
```bash
sudo systemctl --no-pager --full status shine-server caddy
```
-136
View File
@@ -1,136 +0,0 @@
# API для разработчиков: Управление сессиями
Этот файл описывает методы, которые используются уже после успешной авторизации пользователя в сессию.
Здесь два метода:
- `ListSessions` — получить список активных сессий пользователя;
- `CloseActiveSession` — закрыть одну из активных сессий.
Логика раздела такая:
- сначала пользователь проходит `SessionLogin`;
- после этого сервер считает соединение авторизованным;
- уже в этом состоянии клиент может читать список сессий и управлять ими.
То есть это не этап создания или входа в сессию, а этап последующего контроля уже существующих активных сессий.
## 1. `ListSessions`
Доступно только после успешного `SessionLogin`.
### Запрос
```json
{
"op": "ListSessions",
"requestId": "list-001",
"payload": {
}
}
```
### Успешный ответ
```json
{
"op": "ListSessions",
"requestId": "list-001",
"status": 200,
"ok": true,
"payload": {
"sessions": [
{
"sessionId": "sess_7c5e5c4b",
"clientInfoFromClient": "Android 15; Pixel 9",
"clientInfoFromRequest": "UA=Java-http-client/17.0.18; remote=127.0.0.1",
"geo": "RU/Moscow",
"lastAuthenticatedAtMs": 1774600010500
}
]
}
}
```
### Специфические коды ошибок `ListSessions`
- `422 / NOT_AUTHENTICATED` — запрос доступен только после успешного `SessionLogin`.
- `501 / DB_ERROR_LIST_SESSIONS` — ошибка БД при чтении списка активных сессий.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
---
## 2. `CloseActiveSession`
Доступно только после успешного `SessionLogin`.
### Запрос
```json
{
"op": "CloseActiveSession",
"requestId": "close-001",
"payload": {
"sessionId": "sess_7c5e5c4b"
}
}
```
### Успешный ответ
```json
{
"op": "CloseActiveSession",
"requestId": "close-001",
"status": 200,
"ok": true,
"payload": {
}
}
```
### Специфические коды ошибок `CloseActiveSession`
- `422 / NOT_AUTHENTICATED` — запрос доступен только после успешного `SessionLogin`.
- `400 / NO_SESSION_TO_CLOSE` — сервер не смог определить, какую сессию нужно закрыть.
- `501 / DB_ERROR` — ошибка БД при поиске сессии или её удалении.
- `422 / SESSION_NOT_FOUND` — целевая сессия не найдена.
- `422 / SESSION_OF_ANOTHER_USER` — нельзя закрывать сессию другого пользователя.
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
---
## 3. Пример ошибки
```json
{
"op": "CloseActiveSession",
"requestId": "close-001",
"status": 403,
"ok": false,
"error": "NOT_AUTHENTICATED",
"message": "Операция доступна только для авторизованных пользователей",
"payload": {
}
}
```
## 4. Формат `sessionId`
Текущее серверное значение `sessionId` генерируется как:
- случайные **32 байта** (`SecureRandom`),
- кодирование в **стандартный Base64 RFC 4648** (алфавит `A-Z a-z 0-9 + /`),
- **без padding** `=`.
Практически это строка длиной около **43 символов** (для 32 байт без `=`).
Пример реального формата:
```
K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M
```
Важно: это **не человеко-читаемое имя**, а непрозрачный идентификатор.
Нужно передавать его как есть, без нормализации регистра и без URL-экранирования внутри JSON.
-333
View File
@@ -1,333 +0,0 @@
# API для разработчиков: Технические запросы
Этот файл описывает технические WebSocket-запросы, которые нужны для служебной работы клиента с сервером. Часть операций доступна без авторизации, часть требует успешной авторизованной сессии.
Сейчас здесь шесть методов:
- `Ping` — keep-alive запрос для поддержания живого WebSocket-соединения;
- `GetServerInfo` — запрос базовой публичной информации о сервере для выбора узла в децентрализованной сети;
- `GetCallIceConfig` — выдача STUN/TURN конфигурации для звонков;
- `ClientErrorLog` — отправка клиентской ошибки в серверный лог;
- `ClientDebugLog` — отправка клиентского debug-события в серверный буфер;
- `CallDeliveryReport` — диагностический отчёт клиента о доставке/установке звонка.
Логика раздела такая:
- `Ping` нужен для регулярной проверки, что соединение всё ещё живо;
- `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.
Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов.
## 1. `Ping`
### Назначение
Служебный keep-alive запрос.
Клиент может отправлять его периодически, чтобы:
- поддерживать активное WebSocket-соединение;
- понимать, что сервер отвечает;
- при необходимости получать текущее серверное время.
### Запрос
```json
{
"op": "Ping",
"requestId": "ping-001",
"payload": {
"ts": 1774700000123
}
}
```
Поле `ts` в запросе необязательно для логики сервера. Сервер его не валидирует и не использует для принятия решения.
### Успешный ответ
```json
{
"op": "Ping",
"requestId": "ping-001",
"status": 200,
"ok": true,
"payload": {
"ts": 1774700000456
}
}
```
### Специфические коды ошибок `Ping`
- У `Ping` нет специальных прикладных ошибок.
- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.
---
## 2. `GetServerInfo`
### Назначение
Запрос публичной информации о сервере.
Он нужен клиенту для выбора сервера в децентрализованной сети. По этому запросу клиент может:
- проверить, что сервер вообще доступен;
- показать URL и версию сервера;
- показать физический регион или адрес размещения;
- показать описание сервера;
- показать поле `origin` как комментарий о природе этого узла;
- показать дополнительную текстовую информацию.
Этот запрос доступен без авторизации.
### Источник данных
- `version` берётся из Gradle build и подставляется в `application.properties`;
- остальные поля читаются из настроек сервера;
- если значение в конфиге не задано, сервер возвращает пустую строку.
### Запрос
```json
{
"op": "GetServerInfo",
"requestId": "srv-001",
"payload": {
}
}
```
### Успешный ответ
```json
{
"op": "GetServerInfo",
"requestId": "srv-001",
"status": 200,
"ok": true,
"payload": {
"url": "wss://node.example.org/ws",
"version": "1.0",
"physicalRegion": "Грузия, Тбилиси",
"description": "Public community SHiNE node",
"origin": "Community-operated node",
"extraInfo": "IPv4 + IPv6; test federation enabled"
}
}
```
### Поля ответа
- `url` — публичный URL сервера.
- `version` — версия сервера из Gradle build.
- `physicalRegion` — физический регион или адрес размещения сервера.
- `description` — человекочитаемое описание сервера.
- `origin` — комментарий о том, какой это сервер.
- `extraInfo` — любая дополнительная информация о сервере.
### Специфические коды ошибок `GetServerInfo`
- У `GetServerInfo` нет специальных прикладных ошибок при штатной работе.
- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.
---
## 3. `GetCallIceConfig`
Доступно только после успешной авторизации.
### Запрос
```json
{
"op": "GetCallIceConfig",
"requestId": "ice-001",
"payload": {
}
}
```
### Успешный ответ
```json
{
"op": "GetCallIceConfig",
"requestId": "ice-001",
"status": 200,
"ok": true,
"payload": {
"stunUrls": ["stun:stun.example.org:3478"],
"turnUrls": ["turn:turn.example.org:3478?transport=udp"],
"turnUsername": "user",
"turnPassword": "password",
"turnServers": [
{
"id": "primary",
"urls": ["turn:turn.example.org:3478?transport=udp"],
"username": "user",
"password": "password"
}
],
"turnEnabled": true,
"generatedAtMs": 1774700000123,
"expiresAtMs": 1774700300123,
"ttlSec": 300
}
}
```
### Специфические коды ошибок `GetCallIceConfig`
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
---
## 4. `ClientErrorLog`
### Запрос
```json
{
"op": "ClientErrorLog",
"requestId": "err-001",
"payload": {
"kind": "global_error",
"message": "TypeError: failed",
"stack": "...",
"sourceUrl": "https://shineup.me/app.js",
"lineNumber": 10,
"columnNumber": 20,
"route": "#/channel-view/own-0",
"href": "https://shineup.me/#/channel-view/own-0",
"userAgent": "...",
"clientTs": 1774700000123,
"requestOp": "GetChannelMessages",
"requestIdRef": "GetChannelMessages-123",
"contextJson": "{\"screen\":\"channels\"}"
}
}
```
### Успешный ответ
```json
{
"op": "ClientErrorLog",
"requestId": "err-001",
"status": 200,
"ok": true,
"payload": {
"serverTs": 1774700000456,
"accepted": true
}
}
```
### Специфические коды ошибок `ClientErrorLog`
- `400 / BAD_FIELDS` — обязательные поля ошибки не заполнены.
---
## 5. `ClientDebugLog`
### Запрос
```json
{
"op": "ClientDebugLog",
"requestId": "dbg-001",
"payload": {
"runId": "ui-run-1",
"level": "info",
"message": "opened channels tab",
"details": "{\"route\":\"#/channels\"}"
}
}
```
### Успешный ответ
```json
{
"op": "ClientDebugLog",
"requestId": "dbg-001",
"status": 200,
"ok": true,
"payload": {
"accepted": true,
"serverTs": 1774700000456
}
}
```
### Специфические коды ошибок `ClientDebugLog`
- `400 / BAD_FIELDS` — поле `message` не заполнено.
---
## 6. `CallDeliveryReport`
### Запрос
```json
{
"op": "CallDeliveryReport",
"requestId": "call-report-001",
"payload": {
"type": "outgoing_failed",
"value": "{\"reason\":\"ice_failed\",\"callId\":\"call-1\"}"
}
}
```
### Успешный ответ
```json
{
"op": "CallDeliveryReport",
"requestId": "call-report-001",
"status": 200,
"ok": true,
"payload": {
"serverTs": 1774700000456,
"accepted": true
}
}
```
### Специфические коды ошибок `CallDeliveryReport`
- `400 / BAD_FIELDS` — поле `type` не заполнено.
---
## 7. Короткое резюме
- `Ping` нужен для keep-alive и проверки, что WebSocket-соединение живо.
- `GetServerInfo` нужен для выбора сервера в сети и показа публичной информации об узле.
- `GetCallIceConfig` нужен для WebRTC-звонков и требует авторизации.
- `ClientErrorLog`, `ClientDebugLog`, `CallDeliveryReport` используются для диагностики клиента и звонков.
## 8. Прямое техническое сообщение в конкретную сессию
На текущий момент в публичном JSON API этого документа **нет отдельного RPC** для отправки произвольного технического сообщения в конкретную сессию пользователя (по `sessionId`).
Что уже есть в системе:
- сервер хранит `sessionId` активной сессии;
- есть `ListSessions`, чтобы клиент получил список sessionId своего пользователя;
- у сервера есть внутренний реестр активных WS-подключений по `sessionId`.
Чего не хватает для полноценной фичи «direct tech message by sessionId»:
1. отдельная API-операция (например, `SendSessionTechMessage`);
2. правило авторизации (кто имеет право писать в чужую/свою сессию);
3. унифицированный формат payload и события доставки;
4. коды ошибок (`SESSION_OFFLINE`, `SESSION_NOT_FOUND`, `FORBIDDEN` и т.п.).
Итог: как инфраструктурная база это почти готово, но нужен отдельный RPC-слой и политика доступа.
@@ -1,306 +0,0 @@
# API для разработчиков: DM, push и сигналы звонков
Документ описывает WebSocket-операции для подписанных личных сообщений, WebPush и realtime-сигналов звонков.
Логика личных сообщений дополнительно описана в `Dev_Docs/Personal_Messages/README.md`; этот файл фиксирует именно публичные `op`, поля запросов и поля ответов.
## 1. `UpsertPushToken`
Требует авторизации.
### Запрос
```json
{
"op": "UpsertPushToken",
"requestId": "push-upsert-001",
"payload": {
"sessionId": "SESSION_ID",
"endpoint": "https://push.example/...",
"p256dhKey": "BASE64",
"authKey": "BASE64",
"platform": "web",
"userAgent": "Mozilla/5.0 ..."
}
}
```
### Успешный ответ
```json
{
"op": "UpsertPushToken",
"requestId": "push-upsert-001",
"status": 200,
"ok": true,
"payload": {
"tokenId": "token-1",
"updatedAtMs": 1774700000123
}
}
```
---
## 2. `SendTestWebPush`
Требует авторизации. Если `login` передан, он должен совпадать с логином текущей сессии.
### Запрос
```json
{
"op": "SendTestWebPush",
"requestId": "push-test-001",
"payload": {
"login": "alice",
"sessionId": "SESSION_ID",
"title": "Test",
"text": "Push body"
}
}
```
### Успешный ответ
```json
{
"op": "SendTestWebPush",
"requestId": "push-test-001",
"status": 200,
"ok": true,
"payload": {
"targetLogin": "alice",
"attemptedSessions": 1,
"sessionsWithPushConfig": 1,
"delivered": 1,
"failed": 0,
"sentAtMs": 1774700000123
}
}
```
---
## 3. `SendDirectMessage`
Отправляет один подписанный DM-пакет.
### Запрос
```json
{
"op": "SendDirectMessage",
"requestId": "dm-001",
"payload": {
"blobB64": "BASE64_SIGNED_DM_PACKET"
}
}
```
### Успешный ответ
```json
{
"op": "SendDirectMessage",
"requestId": "dm-001",
"status": 200,
"ok": true,
"payload": {
"messageId": "dm-1",
"deliveredWsSessions": 1,
"deliveredWebPushSessions": 0,
"sessionNotFound": false
}
}
```
---
## 4. `SendMessagePair` и `ReceiveOutcomingMessage`
`ReceiveOutcomingMessage` сейчас является алиасом `SendMessagePair` и использует тот же request/handler.
### Запрос
```json
{
"op": "SendMessagePair",
"requestId": "dm-pair-001",
"payload": {
"incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK",
"outgoingBlobB64": "BASE64_OUTGOING_SIGNED_BLOCK"
}
}
```
### Успешный ответ
```json
{
"op": "SendMessagePair",
"requestId": "dm-pair-001",
"status": 200,
"ok": true,
"payload": {
"baseKey": "base-key",
"incomingKey": "incoming-key",
"outgoingKey": "outgoing-key",
"deliveredWsSessions": 1,
"deliveredWebPushSessions": 0
}
}
```
---
## 5. `ReceiveIncomingMessage`
Принимает входящий подписанный DM-блок.
### Запрос
```json
{
"op": "ReceiveIncomingMessage",
"requestId": "dm-in-001",
"payload": {
"incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK"
}
}
```
### Успешный ответ
```json
{
"op": "ReceiveIncomingMessage",
"requestId": "dm-in-001",
"status": 200,
"ok": true,
"payload": {
"messageKey": "incoming-key",
"baseKey": "base-key",
"deliveredWsSessions": 1,
"deliveredWebPushSessions": 0
}
}
```
---
## 6. `AckSessionDelivery`
Требует авторизации. Подтверждает доставку сообщения в текущую сессию.
### Запрос
```json
{
"op": "AckSessionDelivery",
"requestId": "ack-001",
"payload": {
"messageKey": "incoming-key"
}
}
```
### Успешный ответ
```json
{
"op": "AckSessionDelivery",
"requestId": "ack-001",
"status": 200,
"ok": true,
"payload": {
"messageKey": "incoming-key"
}
}
```
---
## 7. `CallInviteBroadcast`
Требует авторизации. Отправляет приглашение к звонку на активные сессии пользователя `toLogin`.
### Запрос
```json
{
"op": "CallInviteBroadcast",
"requestId": "call-invite-001",
"payload": {
"toLogin": "bob",
"callId": "call-1",
"type": 100
}
}
```
### Успешный ответ
```json
{
"op": "CallInviteBroadcast",
"requestId": "call-invite-001",
"status": 200,
"ok": true,
"payload": {
"callId": "call-1",
"deliveredWsSessions": 1,
"deliveredFcmSessions": 0,
"deliveredWebPushSessions": 0
}
}
```
---
## 8. `CallSignalToSession`
Требует авторизации. Отправляет сигнал звонка в конкретную сессию получателя.
### Запрос
```json
{
"op": "CallSignalToSession",
"requestId": "call-signal-001",
"payload": {
"toLogin": "bob",
"targetSessionId": "SESSION_ID",
"callId": "call-1",
"type": 101,
"data": "{\"sdp\":\"...\"}"
}
}
```
### Успешный ответ
```json
{
"op": "CallSignalToSession",
"requestId": "call-signal-001",
"status": 200,
"ok": true,
"payload": {
"delivered": true
}
}
```
Если целевая сессия не найдена или доставка не удалась, сервер может вернуть `404`.
## Типовые ошибки
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
- `400 / BAD_FIELDS` — не заполнены обязательные поля.
- `404 / USER_NOT_FOUND` — пользователь не найден.
- `404 / SESSION_NOT_FOUND` — сессия не найдена.
- `422 / BAD_SIGNATURE` — подпись DM не прошла проверку.
- `422 / BAD_DEVICE_KEY` — некорректный device key отправителя.
- `422 / BAD_TIME_WINDOW` — время подписанного сообщения вне допустимого окна.
- `422 / REPLAY` — повторное сообщение заблокировано.
-48
View File
@@ -1,48 +0,0 @@
# История изменений документации блокчейна
## 2026-05-24 11:40:00 +0300
- Базовый коммит-ориентир: `abdce05`.
- `TEXT_REPOST (subType=30)` оставлен как зарезервированный формат, но новые блоки репоста временно отключены на уровне `AddBlock`.
- В `11_TEXT_Blocks.md` зафиксировано, что запись `TEXT_REPOST` временно не используется до будущей реализации.
- В `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md` добавлен код отказа `repost_disabled`.
## 2026-05-21 19:05:00 +0300
- Базовый коммит-ориентир: `5344c42`.
- Добавлен новый TEXT-подтип `TEXT_REPOST (subType=30)`:
- обновлён перечень типов в `11_TEXT_Blocks.md`;
- обновлена быстрая карта типов в `00_Blockchain_Formats_and_Block_Types.md`.
- Уточнено API-описание поддержанных подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
- В документе `Dev_Docs/API/08_MCP_Чтение_и_дозапись_персонального_публичного_чата.md` зафиксировано, что чтение канала учитывает `TEXT_POST` и `TEXT_REPOST`.
## 2026-05-20 11:34:17 +0300
- Базовый коммит-ориентир: `a53444b`.
- В `13_CONNECTION_Blocks.md` добавлены новые CONNECTION подтипы:
- `60/61``known_person / unknown_person` (знаю этого человека);
- `70/71``shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий);
- `74/75``shine_seen / shine_unseen` (мало знаком, но видел сияющим).
- Обновлён список CONNECTION-подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
## 2026-05-19 20:30:21 +0300
- Базовый коммит-ориентир: `7986184`.
- Уточнён документ `11_TEXT_Blocks.md`: для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` зафиксировано, что `textLen=0` допустим и трактуется как логическое удаление сообщения.
- Явно закреплено, что отдельного `DELETE`-подтипа нет, удаление выполняется edit-блоком.
## 2026-05-19 00:22:46 +0300
- Базовый коммит-ориентир: `c27da63a3e65`.
- Актуализирован `README.md` как точка входа для MVP-документации по протоколу.
- В документации явно зафиксировано, что `channelType=100` и `channelType=200` присутствуют в формате, но пока не используются в UI.
- Актуализирован перечень REACTION-подтипов: добавлен `REACTION_UNLIKE (subType=2)`.
- Актуализирован перечень CONNECTION-подтипов: добавлены `SPOUSE/PARENT/CHILD/SIBLING` и обратные операции.
- В документ `02_Blockchain_Kinds_and_Lines.md` добавлены фактические серверные правила валидации line-полей.
- Обновлён корневой `AGENTS.md`: формат блокчейна менять только после явного подтверждения пользователя и с предварительным предупреждением.
## 2026-05-13 00:02:32 +0300
- Базовый коммит-ориентир: `f63f40f1eb2f`.
- Добавлен текущий формат `CreateChannelBody` с полями `channelType (2 байта)` и `channelTypeVersion (2 байта)`.
- Зафиксированы типы каналов: `0=stories`, `1=public`, `100=personal`, `200=group`.
- Серверная уникальность имени канала изменена на `owner + type + name(slug)`.
- Root-канал `0` переименован в `stories` на уровне API-чтения.
- Для персонального канала (`type=100`) включена сборка парного потока при чтении (`A->B` + `B->A`, если существует).
- Добавлена поддержка командного префикса `/.` и команды `/.desc` для актуализации описания канала при чтении.
- Зафиксированы команды `/.add` и `/.remove` для каналов `type=200` (зарезервировано под расширение участниками).
- В `AGENTS.md` добавлено обязательное правило актуализации документации в `Dev_Docs/Blockchain/`.
-100
View File
@@ -1,100 +0,0 @@
# Синхронизация блоков и DM между серверами SHiNE
Документ описывает архитектуру и протокол синхронизации данных между партнёрскими серверами SHiNE.
## 1. Зачем нужна синхронизация
Пользователи SHiNE могут быть «приписаны» к разным серверам.
Когда пользователь A (на сервере X) пишет пользователю B (на сервере Y):
1. Сервер X принимает сообщение;
2. Сервер X должен переслать DM-блок серверу Y;
3. Сервер Y сохраняет блок и доставляет в активные сессии пользователя B.
Аналогично, блоки пользовательского блокчейна (записи `AddBlock`) должны синхронизироваться,
чтобы любой партнёрский сервер мог отдать полную историю пользователя.
## 2. Список серверов синхронизации (`sync_servers`)
Каждый сервер регистрирует в своей Solana PDA список `sync_servers`
логины SHiNE-аккаунтов партнёрских серверов, с которыми он синхронизируется.
- Список хранится в блоке `ServerProfileBlock` внутри `user_pda` сервера.
- Адрес каждого партнёрского сервера читается из его PDA на Solana.
- Синхронизация двусторонняя: оба сервера должны иметь друг друга в `sync_servers`.
## 3. Что синхронизируется
### 3.1 Личные сообщения (DM)
- Все DM-блоки форматов типов `1/2` (текст) и `3/4` (read-receipt).
- Сервер-отправитель: при получении пары блоков от клиента перенаправляет их серверу получателя.
- Сервер-получатель: сохраняет блоки в `signed_messages_v2`, доставляет в активные сессии.
- Дедупликация по уникальному `message_key = from|to|timeMs|nonce|type`.
### 3.2 Блоки пользовательского блокчейна
- Все блоки `AddBlock` пользователей, зарегистрированных на сервере или синхронизирующихся через него.
- Синхронизируются в обе стороны между всеми партнёрами из `sync_servers`.
- Порядок блоков сохраняется (по глобальному номеру блока и хэшу).
- Дедупликация по глобальному номеру блока и хэшу.
## 4. Протокол синхронизации (целевой, не реализован)
### 4.1 Межсерверное соединение
- Серверы устанавливают постоянное WebSocket-соединение друг с другом.
- Адрес партнёра определяется по `server_address` из его Solana PDA.
- Аутентификация: подпись Ed25519 корневым ключом сервера (`root_key` из PDA).
- При разрыве — переподключение с экспоненциальным backoff.
### 4.2 Доставка новых данных (push)
- При получении нового блока или DM сервер немедленно пушит его всем подключённым партнёрам.
- Партнёр подтверждает приём (ACK). Без ACK — повтор с backoff.
### 4.3 Начальная синхронизация (backfill)
- При первом подключении к партнёру серверы обмениваются «курсорами» состояния:
последний глобальный номер блока, последний известный DM-ключ.
- Сервер с более полной историей досылает недостающее партнёру.
### 4.4 Разрешение конфликтов
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `Dev_Docs/Blockchain/README.md`).
- DM: конфликтов нет, `message_key` уникален.
## 5. Маршрутизация DM между серверами
При отправке DM от пользователя A к пользователю B:
1. Клиент A отправляет пару блоков на свой сервер X.
2. Сервер X определяет, на каком сервере зарегистрирован пользователь B.
- Сначала проверяет локально (если B зарегистрирован на X).
- Иначе читает PDA пользователя B из Solana и смотрит `access_servers`.
- Выбирает первый доступный сервер из `access_servers` и перенаправляет туда DM.
3. Сервер Y (из `access_servers` B) сохраняет и доставляет блоки.
Кэш адресов серверов: обновляется раз в сессию (при ошибке соединения).
## 6. Безопасность
- Все блоки подписаны ключами пользователя на клиенте — сервер не может подделать содержимое.
- Серверы не расшифровывают DM-контент (шифрование — задача следующего этапа).
- При синхронизации каждый блок проходит валидацию подписи на принимающем сервере.
## 7. Статус реализации
| Компонент | Статус |
|-----------|--------|
| Регистрация серверной PDA в Solana | ✅ Реализовано |
| Чтение `sync_servers` из PDA | Нужна реализация |
| Межсерверный WebSocket-канал | Нужна реализация |
| Push новых DM партнёрам | Нужна реализация |
| Push блоков блокчейна партнёрам | Нужна реализация |
| Backfill при первом подключении | Нужна реализация |
| Маршрутизация DM через access_servers | Нужна реализация (заглушка) |
Текущая версия сервера работает без межсерверной синхронизации.
Синхронизация — задача следующего этапа разработки.
-48
View File
@@ -1,48 +0,0 @@
# Будущие фичи
Эта папка хранит задачи, которые сознательно отложены и сейчас не должны попадать в активную разработку или ручную проверку без отдельной команды пользователя.
## Горизонты планирования
- `near/` - ближайшие планы: задачи, к которым можно вернуться сегодня или завтра.
- `medium/` - среднесрочные планы: задачи на ближайшие недели или 1-2 месяца.
- `far/` - дальнее будущее: идеи без понятного срока возврата.
Если пользователь спрашивает, какие есть планы, агент должен смотреть эти три папки и кратко перечислять задачи по горизонтам.
## Как использовать
1. Каждая будущая фича описывается отдельным markdown-файлом в одном из горизонтов.
2. В файле нужно фиксировать:
- зачем нужна фича;
- к какому сроку или горизонту она относится;
- что нужно сделать;
- какие вопросы нужно уточнить перед реализацией;
- что уже было сделано в коде, если фича частично реализована;
- что временно отключено или закомментировано, если применимо;
- какие документы нужно обновить при возврате к задаче;
- с какого места продолжать разработку.
3. Агент не должен начинать реализацию файлов из этой папки без явной просьбы пользователя.
## Текущие планы
### Ближайшие
- `near/2026-05-25_1106_telegram_agent_players.md` - разрешённые пользователи Telegram для агента, отдельные папки игроков, персональные истории и публикация краткого вопроса/ответа в общий канал.
- `near/2026-05-25_1106_wallet_topup_solana_arweave.md` - пополнение Solana и Arweave через внешний сервис покупки с подсказкой и копированием адреса.
### Среднесрочные
- `medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md` - репосты в каналах и тредах.
- `medium/2026-05-25_1106_shine_balance_wallet.md` - кошелёк и пополнение баланса сияния через блокчейн.
- `medium/2026-05-26_0029_esp32s3_file_storage.md` - ESP32S3 как личное файловое хранилище SHiNE для файлов переписок и вложений.
- `medium/2026-06-03_подключение_других_устройств_через_qr.md` - довести подключение других устройств через QR: сейчас заготовка есть, но сценарий работает нестабильно и его нужно будет отдельно доделать.
- `medium/2026-06-02_сессионные_саб_серверы_в_pda.md` - несколько саб-серверов пользователя как типизированные сессии в PDA с версией записи.
### DAO-запуск
- `dao_запуск/2026-06-05_esp32_hardware_wallet_device_session.md` - ESP32 как аппаратный кошелёк: постоянная device-сессия на сервере, подтверждение операций на экране, делегированные сессии для браузера/телефона.
### Дальнее будущее
- Сейчас задач нет.
-5
View File
@@ -1,5 +0,0 @@
# Дальнее будущее
Сейчас в этом горизонте нет активных идей.
Сюда переносить задачи, у которых нет понятного срока возврата и которые не нужно учитывать в ближайшем или среднесрочном планировании.
@@ -1,94 +0,0 @@
# Telegram-агент для разрешённых игроков
- Горизонт:
`near`
- Ориентир:
сегодня/завтра
- Статус:
`proposal`
## Кратко
Нужно расширить `SHiNE-agent-bot-coder`, чтобы агент мог принимать личные сообщения от заранее разрешённых пользователей, вести по каждому отдельную рабочую папку и историю, помогать им с обсуждениями/документами без изменения кода, а краткий результат публиковать в общий канал.
## Пользовательский сценарий
1. Разрешённый пользователь пишет агенту в личные сообщения текстом или голосом.
2. Голосовое сообщение распознаётся так же, как сейчас распознаются voice/audio-задачи.
3. Сервис определяет пользователя по разрешённому списку логинов.
4. Для пользователя используется отдельная папка в `Players/`.
5. Codex запускается с системным контекстом: от имени какого человека он работает, где лежит его папка, какие у него локальные инструкции.
6. Агент может читать код и документацию проекта, но писать должен только в папку этого пользователя, если нет отдельного согласования на изменение общего проекта.
7. После ответа пользователю агент отправляет в общий канал короткую сводку двумя сообщениями или двумя блоками: вопрос пользователя и полученный ответ.
8. Команда `/new` или `New` сбрасывает только сессию этого пользователя.
## Предлагаемая структура
- `Players/`
- `Ivan/`
- `AGENTS.md`
- `history/`
- `files/`
- `Sergey/`
- `AGENTS.md`
- `history/`
- `files/`
- `Milana/`
- `AGENTS.md`
- `history/`
- `files/`
Имена папок можно уточнить после получения точных Telegram-логинов.
## Что нужно сделать
1. Добавить конфигурацию разрешённых Telegram-пользователей.
2. Описать соответствие `telegram username -> имя игрока -> папка`.
3. Создавать или использовать отдельную историю диалога для каждого игрока.
4. Поддержать личные сообщения от разрешённых пользователей.
5. Запретить постановку задач от неизвестных пользователей.
6. Для групп/каналов оставить текущую логику: команды Айдара имеют приоритет.
7. При запуске Codex для игрока добавлять отдельный системный контекст:
- имя пользователя;
- путь к его папке;
- правило записи только в эту папку;
- путь к персональному `AGENTS.md`.
8. После ответа игроку отправлять краткую сводку в общий канал.
9. Поддержать `/new`/`New` как сброс только персональной сессии игрока.
10. Добавить защиту от случайного изменения общего кода в режиме игрока.
## Вопросы перед реализацией
1. Точные Telegram-логины Ивана, Сергея и Миланы.
2. Какой общий канал использовать для сводок: текущий `@shine_writing` или отдельный чат.
3. Нужно ли отправлять в общий канал полный текст вопроса/ответа или краткую выжимку.
4. Нужно ли пересылать вложения игроков в общий канал или только текстовые сводки.
5. Разрешить ли игрокам читать все документы проекта, включая технические заметки деплоя.
6. Что делать, если пользователь просит изменить код: отказать, создать предложение в своей папке или просить подтверждение Айдара.
7. Нужны ли русские имена папок (`Иван`, `Сергей`, `Милана`) или ASCII-имена (`Ivan`, `Sergey`, `Milana`).
8. Нужно ли хранить истории игроков в общей папке сервиса или внутри `Players/<name>/history/`.
## Риски и ограничения
- Нужно аккуратно разделить режим Айдара и режим игрока, чтобы игроки не могли случайно запустить изменение общего кода.
- Нужно не смешать истории разных пользователей.
- Нужно ограничить публикацию в общий канал, чтобы не утекали личные или слишком длинные ответы.
- Нужна проверка Telegram-идентификации: username может меняться, поэтому желательно хранить и `user_id`.
## Документы, которые обновить при реализации
- `SHiNE-agent-bot-coder/AGENTS.md`
- `SHiNE-agent-bot-coder/AGENT.md`
- `SHiNE-agent-bot-coder/README.md`
- `Dev_Docs/deploy/agent-bot-coder-local-systemd.md`, если появятся новые переменные окружения или настройки сервиса.
## Минимальная проверка
1. Айдар по-прежнему может ставить задачи из `@shine_writing`.
2. Неизвестный пользователь не ставит задачу в очередь.
3. Разрешённый игрок пишет личное текстовое сообщение и получает ответ.
4. Разрешённый игрок отправляет voice, оно распознаётся и обрабатывается.
5. История одного игрока не попадает в историю другого.
6. `/new` сбрасывает только историю текущего игрока.
7. Сводка вопрос/ответ появляется в общем канале.
8. В режиме игрока агент не пишет за пределы `Players/<name>/` без отдельного подтверждения.
@@ -1,43 +0,0 @@
# Кошелёк: лимит/закрепление блокчейна Сияния
- статус: `pending`
## Кратко что сделано
- На экране `Кошелёк -> Блокчейн Сияния` добавлены 2 слоя данных:
- фактическое состояние цепочки на сервере (`кол-во блоков`, `размер`, `крайний блок`, `hash`, `размер крайнего блока`);
- закреплённое состояние в Solana PDA (`лимит`, `использовано`, `остаток`, `крайний блок`, `hash`).
- Добавлены действия:
- `Закрепить в Solana` — обновляет PDA до текущего состояния серверной цепочки;
- `Увеличить лимит` — увеличивает `paid_limit_bytes` в PDA с учётом цены из economy PDA.
- Если `rootKey`/`blockchainKey` не сохранены локально, экран запрашивает пароль, восстанавливает ключи через стандартную derivation-логику и предлагает сохранить их в зашифрованный контейнер.
## Что проверять вручную
1. Открыть `Кошелёк -> Блокчейн Сияния` под авторизованным пользователем.
2. Проверить, что в блоке "Фактическое состояние на сервере" отображаются:
- число блоков;
- размер цепочки;
- номер/хэш крайнего блока;
- размер крайнего блока.
3. Проверить, что в блоке "Закреплено в Solana" отображаются:
- лимит;
- израсходовано;
- остаток;
- номер/хэш крайнего закреплённого блока.
4. Нажать `Закрепить в Solana` и убедиться, что:
- приходит успешная транзакция;
- после обновления Solana-показатели подтягиваются до серверных (или максимально близко по актуальному состоянию).
5. Нажать `Увеличить лимит`, ввести значение кратное шагу, подтвердить списание и проверить:
- лимит увеличился;
- отображение цены/списания соответствует economy PDA.
6. Повторить пункты 4-5 в сценарии, когда `rootKey`/`blockchainKey` не сохранены, и проверить:
- появляется запрос пароля;
- после ввода пароля операции выполняются;
- предложение сохранить ключи показывается.
## Ожидаемый результат
- Экран корректно разделяет "фактическое состояние на сервере" и "закреплённое в Solana".
- Обе операции (`Закрепить в Solana`, `Увеличить лимит`) выполняются без ошибок при валидных данных.
- Восстановление ключей через пароль работает, а без нужных ключей операция не выполняется молча.
@@ -1,29 +0,0 @@
# Озвучивание ответов агента
## Что сделано
В локальный Telegram-бот-сервис агента-кодера добавлены персональные настройки озвучивания финальных ответов:
- `/voice_on` включает озвучивание для текущего Telegram-пользователя;
- `/voice_off` выключает озвучивание для текущего Telegram-пользователя;
- `/voice_status` показывает текущее состояние;
- если озвучивание включено, после текстового финального ответа сервис генерирует voice-файл через OpenAI TTS и отправляет его в Telegram;
- длинные ответы делятся на несколько фрагментов озвучки.
## Что проверять
1. Перезапустить `shine-agent-bot-coder`.
2. Отправить `/voice_status` и убедиться, что по умолчанию озвучивание выключено.
3. Отправить `/voice_on`.
4. Дать простую задачу агенту и проверить, что пришёл полный текстовый ответ и voice-файл с тем же ответом.
5. Отправить `/voice_off`.
6. Дать ещё одну простую задачу и проверить, что приходит только текст.
7. При возможности проверить второго whitelist-пользователя: его настройка должна быть независимой.
## Ожидаемый результат
Настройка хранится персонально по username и сохраняется после перезапуска сервиса. При включённой настройке Telegram получает текстовый ответ и дополнительное voice-сообщение с озвучкой. При выключенной настройке поведение остаётся прежним.
## Статус
pending
@@ -1,19 +0,0 @@
# Голосовая адаптация ответов Telegram-бота
## Краткое описание
Добавлены персональные настройки голосовых ответов и адаптации текста перед озвучкой. Если голосовые ответы включены, сервис перед TTS может отдельно прогонять финальный текст через OpenAI-модель и отправлять более короткую голосовую версию в исходный чат, личный чат пользователя и общий чат `@shine_writing`, если эти чаты доступны и отличаются.
## Что проверить
- Команды `/voice_on`, `/voice_off`, `/voice_status` для конкретного пользователя.
- Команды `/voice_rewrite_on`, `/voice_rewrite_off`, `/voice_rewrite_status` для конкретного пользователя.
- Команда `/status` показывает очередь, голосовые ответы и адаптацию текста перед озвучкой.
- При включённых голосовых ответах после задачи приходит текстовый ответ и voice-ответ.
- При включённой адаптации voice-ответ короче и без длинных технических строк.
- При задаче из личного чата voice дополнительно появляется в общем чате `@shine_writing`.
- При задаче из общего чата voice дополнительно появляется в личном чате пользователя, если сервис уже знает его личный chat_id.
## Ожидаемый результат
Текстовый ответ остаётся полным. Голосовая версия приходит отдельно, звучит короче и естественнее, а персональные настройки одного пользователя не меняют поведение других пользователей.
## Статус
pending
@@ -1,24 +0,0 @@
# Эксперимент Understand Anything
## Краткое описание
Добавлена изолированная лаборатория для проверки `Lum1104/Understand-Anything` без подключения к сборке, деплою и рабочему коду SHiNE.
## Что проверять
- Установить Node.js 22+ и pnpm 10+.
- Запустить `./tools/understand-anything-lab/install_codex_skills.sh`.
- Перезапустить Codex-сессию.
- Выполнить `/understand --language ru` в корне проекта.
- После генерации выполнить `/understand-dashboard` и проверить, что граф открывается и помогает ориентироваться по серверным, UI, Solana и агентским папкам.
## Ожидаемый результат
- В проекте появляется локальная папка `.understand-anything/` с графом знаний.
- Dashboard открывается и показывает интерактивный граф проекта.
- Основные процессы сборки и деплоя SHiNE не меняются.
## Статус
`pending`
@@ -1,24 +0,0 @@
# Центр задач Telegram-агента
## Краткое описание
Добавлена первая версия центра задач и предложений внутри `SHiNE-agent-bot-coder`.
Бот хранит задачи и предложения в JSON-файле данных сервиса, умеет показывать список через `/tasks`, создавать задачи для игроков по фразе Айдара, принимать предложения игроков по префиксу `предложение:`, менять статусы и добавлять короткие напоминания после ответов.
## Что проверять
- Айдар пишет `/tasks` и видит текущий список задач и предложений без уже закрытого предложения от Димы.
- Айдар пишет `поставь задачу Милане: проверить описание SHiNE` и задача появляется в списке Миланы.
- Милана пишет `/tasks` и видит назначенную задачу.
- Игрок пишет `предложение: ...`, после чего предложение появляется у Айдара.
- Айдар меняет статус фразами вида `одобрить TC-XXXX`, `доработать TC-XXXX`, `закрыть TC-XXXX`, где `TC-XXXX` - ID существующей задачи или предложения.
- После обычного ответа бота Айдару или игроку появляется короткое напоминание, если у пользователя есть активные задачи.
## Ожидаемый результат
Задачи и предложения сохраняются между перезапусками сервиса, статусы меняются корректно, напоминания не мешают основному ответу Codex.
## Статус
pending
@@ -1,31 +0,0 @@
# Рестарты и voice-настройки Telegram-агента
## Краткое описание
Добавлена первая версия безопасного рестарта Telegram-агента:
- `/restart` и `/restart_service` ставят отложенный рестарт после текущей задачи и до взятия следующей;
- `/restart_hard`, `/restart_now`, `/restart_force` выполняют жёсткий рестарт сразу;
- команды рестарта доступны только Айдару;
- voice-ответы включены по умолчанию для новых пользователей;
- адаптация текста перед озвучкой стала ближе к исходному ответу и не должна менять смысл;
- скрыты отдельные команды статуса voice-функций из справки, состояние показывается через `/status`.
## Что проверить
1. Отправить `/restart` во время активной задачи игрока или Айдара.
2. Убедиться, что активная задача завершается, после чего сервис перезапускается до следующей задачи.
3. Отправить `/restart_hard` и убедиться, что сервис перезапускается сразу.
4. Проверить, что игрок не может выполнить команды рестарта.
5. Проверить `/status`: он показывает очередь и состояния голосовых функций.
6. Проверить нового пользователя: voice-ответы должны быть включены по умолчанию.
7. Проверить текстовый запрос пользователя с включённым voice: после текстового ответа должен прийти voice-файл.
8. Проверить, что адаптированная озвучка не превращается в другой ответ, а только убирает длинные технические строки.
## Ожидаемый результат
Сервис можно обновлять без потери текущей задачи через отложенный рестарт. Жёсткий рестарт остаётся аварийной командой Айдара. Voice-ответы работают для текстовых и голосовых запросов, а голосовая версия остаётся близкой к текстовой.
## Статус
pending
@@ -1,26 +0,0 @@
# Кнопки вкладки «Каналы»
## Что сделано
Доработана верхняя панель вкладки «Каналы»:
- при открытии нижней кнопкой «Каналы» показывается режим «Все каналы»;
- в режиме «Все каналы» справа доступны кнопка «Мои каналы» и иконка поиска канала;
- в режиме «Мои каналы» доступен переход обратно во «Все каналы», а справа показывается плюсик создания канала.
## Что проверить
1. Открыть вкладку «Каналы» через нижнюю навигацию.
2. Убедиться, что открыт режим «Все каналы», а плюсик создания канала не отображается.
3. Нажать иконку поиска в режиме «Все каналы».
4. Убедиться, что открывается текущий сценарий поиска каналов.
5. Нажать «Мои каналы».
6. Убедиться, что справа появился плюсик создания канала.
7. Нажать «Все каналы» или стрелку назад и проверить возврат к режиму «Все каналы».
## Ожидаемый результат
Кнопки верхней панели соответствуют активному режиму: поиск в «Все каналы», создание только в «Мои каналы».
## Статус
pending
@@ -1,13 +0,0 @@
# Длинные voice/audio в Telegram-боте агента
- краткое описание фичи:
Бот теперь умеет обрабатывать длинные voice/audio аккуратнее: учитывает лимит Telegram Bot API на скачивание слишком больших файлов, поддерживает альтернативный `TELEGRAM_API_BASE_URL` для локального `telegram-bot-api`, локально пережимает длинное аудио через `ffmpeg`, режет на куски и отправляет их в OpenAI transcription последовательно.
- что именно проверять:
1. Короткий `voice` по-прежнему распознаётся без заметной задержки.
2. Длинный `audio/voice`, который помещается в скачивание Telegram, успешно пережимается, режется на части и даёт цельную расшифровку.
3. Очень большой файл через обычный `https://api.telegram.org` даёт понятное сообщение про лимит Telegram.
4. После переключения на локальный `telegram-bot-api` такой же большой файл начинает скачиваться и распознаваться.
- ожидаемый результат:
Бот не падает на длинных аудио, даёт либо расшифровку, либо понятное объяснение, какой именно лимит мешает и что нужно включить.
- статус:
pending
@@ -1,14 +0,0 @@
# Диагностика больших voice/audio в Telegram-боте
- краткое описание фичи:
- Бот при большом voice/audio больше не отказывается заранее по метаданным Telegram. Теперь он сначала сообщает, что пробует скачать файл, затем отдельно сообщает об успешном скачивании и только после этого переходит к подготовке аудио и распознаванию через OpenAI.
- что именно проверять:
- Отправить в бота большой `voice` или `audio`, который раньше попадал под ранний отказ.
- Проверить, что сначала приходит сообщение о попытке скачать большой файл.
- Проверить два сценария:
- скачивание удалось: бот пишет об успешной загрузке и продолжает распознавание;
- скачивание не удалось: бот пишет именно о неудачном скачивании из Telegram, без ложной привязки к ошибке OpenAI.
- ожидаемый результат:
- Пользователь видит понятную поэтапную диагностику: попытка скачивания, результат скачивания и только потом следующий этап обработки.
- статус:
- pending
@@ -1,24 +0,0 @@
# Перенос server UI в shine-UI
- краткое описание фичи:
Веб-панель управления серверной Solana PDA перенесена в `shine-UI/` как отдельные страницы.
Новая точка входа: `shine-UI/server-ui.html`.
Общая логика работы с PDA вынесена в единый модуль `shine-UI/js/services/shine-user-pda-service.js`.
- что именно проверять:
1. Открытие `shine-UI/server-ui.html` и переходы на страницы создания и обновления PDA.
2. Генерацию ключей из логина и пароля на странице создания.
3. Ручной ввод base58-ключей и регистрацию серверного PDA.
4. Загрузку существующей серверной PDA на странице обновления.
5. Обновление `server_address` и `sync_servers` только по `root + device` без blockchain-ключа.
6. Корректное чтение нового формата `ServerProfileBlock` через общий PDA-модуль.
7. То, что актуальной точкой входа остаётся `shine-UI/server-ui.html`.
- ожидаемый результат:
1. Новые страницы открываются без JS-ошибок.
2. Создание серверной PDA проходит через общий модуль и пишет актуальный формат.
3. Обновление серверной PDA переиспользует существующую подпись LastBlockState и не требует blockchain-ключ.
4. Клиентский UI не ломается после перевода общего PDA-слоя на новый формат.
- статус:
pending
@@ -1,20 +0,0 @@
# Кнопка настройки сервера и DEVNET topup
- краткое описание фичи:
На экране `entry-settings-view` добавлена кнопка `Настроить свой сервер`, открывающая `server-ui.html` в новой вкладке.
На страницах серверного UI добавлена кнопка открытия `devnet-topup-view` в новой вкладке с автоматической передачей `wallet` из device-адреса.
- что именно проверять:
1. На странице настроек входа есть кнопка `Настроить свой сервер`.
2. Кнопка открывает `shine-UI/server-ui.html` в новой вкладке.
3. На страницах `create-server-pda.html` и `update-server-pda.html` есть кнопка `Открыть пополнение DEVNET`.
4. Если device public key заполнен, новая вкладка открывает `devnet-topup-view?wallet=...` с правильным адресом.
5. Если device-адрес не введён, серверный UI показывает понятную ошибку и не открывает пустую ссылку.
- ожидаемый результат:
1. Переход в серверный UI с клиентской страницы настроек работает.
2. Пополнение devnet из серверного UI открывается сразу на нужный адрес.
3. Основной клиентский UI и серверные страницы не получают JS-ошибок при загрузке.
- статус:
pending
@@ -1,15 +0,0 @@
# Фикс DEVNET topup и автоподстановки пароля
- статус: pending
- кратко: исправлена ширина экрана `devnet-topup-view` после успешного пополнения и отключена нежелательная автоподстановка пароля в server UI и на экранах входа/регистрации.
## Что проверять
- Открыть страницу пополнения DEVNET, выполнить пополнение и убедиться, что после появления `Signature` экран не расширяется по ширине.
- Проверить, что кнопки на странице пополнения остаются аккуратными и не разъезжаются.
- Открыть `server-ui/update-server-pda.html`, загрузить PDA и убедиться, что поле пароля остаётся пустым.
- Проверить обычные экраны входа и регистрации: поле пароля не должно самопроизвольно заполняться длинной строкой.
## Ожидаемый результат
- Длинная transaction signature переносится по строкам внутри прежней ширины экрана.
- Кнопки сохраняют компактный mobile-first layout.
- Поля пароля пустые, пока пользователь сам ничего не вводил.
@@ -1,17 +0,0 @@
# Диагностика ключей server PDA и баланс device
- статус: pending
- кратко: на странице обновления server PDA добавлена сверка ожидаемых ключей с уже загруженной PDA, предупреждение о неверном пароле, кнопка показа баланса device-аккаунта и уточнение, что create/update оплачиваются с deviceKey.
## Что проверять
- На `update-server-pda.html` загрузить существующую PDA и убедиться, что видны ожидаемые `root/blockchain/device` public key.
- Ввести правильный пароль и нажать `Сгенерировать`: должно появиться сообщение, что ключи совпадают.
- Ввести неверный пароль и нажать `Сгенерировать`: должно появиться сообщение, что ключи не совпали и пароль, вероятно, неверный.
- На `create-server-pda.html` и `update-server-pda.html` нажать `Показать / обновить баланс device` и убедиться, что баланс читается по текущему `devPub`.
- Повторить `update_user_pda` после увеличения `heap frame` и проверить, ушла ли ошибка `memory allocation failed`.
## Ожидаемый результат
- Пользователь видит, какие именно public key должны получиться для загруженной PDA.
- Ошибка неправильного пароля выявляется до отправки транзакции.
- Баланс device-кошелька читается прямо со страницы.
- Если проблема `OOM` была только в размере heap frame/compute budget клиента, `update_user_pda` начинает проходить.
@@ -1,15 +0,0 @@
# Lazy-import Solana PDA: актуальный формат
- Краткое описание:
Серверный Java lazy-import пользователя из `shine_users` обновлён под актуальный формат `user_pda`. Убран RPC-фильтр по размеру PDA, добавлен разбор нового `ServerProfileBlock` (`block_type = 30`) без сохранения server-only полей в `solana_users`.
- Что проверять:
1. Взять логин пользователя, который существует в Solana PDA, но отсутствует в локальной таблице `solana_users`.
2. Выполнить вход этим логином через сервер.
3. Убедиться, что lazy-import подтянул пользователя из Solana.
4. Убедиться, что запись в `solana_users` создана с полями `login`, `blockchain_name`, `solana_key`, `blockchain_key`, `device_key`.
5. Убедиться, что отсутствие/наличие server-полей в PDA не ломает импорт.
- Ожидаемый результат:
1. Пользователь успешно находится и импортируется из Solana PDA независимо от фактического размера PDA.
2. Новый `ServerProfileBlock` не ломает парсер.
3. В БД не появляются лишние server-only поля.
- Статус: `pending`
@@ -1,33 +0,0 @@
# Pure Rust `shine_users` и `shine_login_guard`
Статус: `pending`
## Что сделано
- `shine_login_guard` переписан без Anchor на чистый Rust/Solana SDK.
- `shine_users` переписан без Anchor на чистый Rust/Solana SDK.
- Для `shine_users` введён новый instruction ABI без Anchor discriminator'ов.
- Для `shine_users` используются новые seed'ы:
- `user_login=` для `user_pda`
- `shine_users_economy_config` для economy PDA
- Формат блоков PDA синхронизирован:
- `SessionsBlock = 50`
- `TrustedStateBlock = 70`
- UI JS-модуль и Java lazy-import обновлены под новые seeds/ABI/коды блоков.
## Что проверить руками
1. В обычном UI выполнить регистрацию нового пользователя в Solana.
2. Проверить, что после регистрации читается новая `user_pda`.
3. В server UI выполнить создание server PDA.
4. В server UI выполнить update server PDA.
5. Проверить, что после update растёт `record_number`.
6. Проверить, что lazy-import на сервере читает новый формат PDA без ошибок.
7. Проверить, что старые Anchor discriminator'ы больше нигде не требуются.
## Ожидаемый результат
- Регистрация и update работают на новых чисто-rust программах.
- UI не использует старый Anchor ABI.
- Серверный Java parser читает новый формат PDA.
- Ошибок `out of memory` и anchor-specific падений больше нет.
@@ -1,25 +0,0 @@
# ESP32 Argon2/UI совместимость и экран результата
- краткое описание фичи:
выравнивание derivation на `ESP32` с текущим `UI` по нормализации логина, совместимости `master secret`/`root.key`/`bch.key`/`dev.key`, а также правки экрана результата и progress bar.
- что именно проверять:
1. На `UI` и `ESP32` ввести один и тот же логин в разном регистре, например `Anya24`, и один и тот же непустой пароль.
2. Убедиться, что после нормализации логина на `ESP32` и `UI` получаются одинаковые:
`master secret`, `root`, `blockchain`, `device` в `Base58`.
3. Проверить режим пустого пароля:
`UI` и `ESP32` должны выдать одинаковые ключи в legacy-режиме.
4. Проверить, что пустой логин на `ESP32` не запускает расчёт и показывает сообщение об ошибке.
5. Проверить progress bar:
при непустом пароле полоса должна быть видна и двигаться.
6. Проверить экран результата:
сначала `Login`, затем `Password`, затем `Master secret` и ключи;
свайп вверх/вниз должен прокручивать длинный результат без артефактов.
- ожидаемый результат:
`ESP32` и `UI` считают одинаковый `master secret` и одинаковые ключи для одинаковых входных данных;
progress bar виден;
экран результата читаемый и корректно прокручивается.
- статус:
pending
@@ -1,17 +0,0 @@
## Краткое описание
В `SHiNE-agent-bot-coder` для личных чатов добавлен режим одного редактируемого статусного сообщения. Бот принимает запрос, обновляет это сообщение по этапам обработки и в конце превращает его в финальный текстовый ответ. При длинном ответе допускается ещё одно дополнительное текстовое сообщение с продолжением. Голосовой ответ остаётся отдельным сообщением.
## Что проверять
1. Отправить в личный чат короткий текстовый запрос и убедиться, что бот не шлёт цепочку промежуточных сообщений, а редактирует одно сообщение до финального ответа.
2. Отправить в личный чат `voice` или `audio` и убедиться, что в том же сообщении последовательно видны этапы распознавания и выполнения.
3. Проверить длинный ответ, который не помещается в один Telegram message: должно получиться не больше двух текстовых сообщений.
4. Проверить, что `voice`-ответ приходит отдельным новым сообщением после текстового.
5. Проверить, что в `@shine_writing` по-прежнему логируются только итоговые `вопрос -> ответ`, без промежуточных статусов.
## Ожидаемый результат
- В личке основная переписка стала чище: промежуточные этапы живут в одном редактируемом сообщении.
- При длинном ответе бот не разбрасывает ответ на много сообщений.
- Канал `@shine_writing` работает по старой схеме без лишнего шума.
## Статус
`pending`
@@ -1,24 +0,0 @@
## Краткое описание
В локальный Telegram-бот `SHiNE-agent-bot-coder` добавлена команда `/settings`, которая сразу показывает текущие персональные настройки пользователя и список доступных команд для их изменения. В `/help` оставлена только ссылка на `/settings` без перечисления самих команд настроек. Также добавлен переключатель режима ответа в личке: один редактируемый статус или отдельные сообщения по этапам.
## Что проверять
1. Отправить `/help` и убедиться, что в справке есть `/settings`, но нет списка команд `/voice_*` и `/single_message_*`.
2. Отправить `/settings` и проверить, что бот показывает текущие значения:
- озвучивание финальных ответов;
- адаптацию текста перед озвучкой;
- режим одного редактируемого сообщения в личке.
3. По очереди переключить:
- `/voice_on` и `/voice_off`;
- `/voice_rewrite_on` и `/voice_rewrite_off`;
- `/single_message_on` и `/single_message_off`.
4. После каждого переключения снова вызвать `/settings` и убедиться, что статус изменился и сохранился.
5. При `/single_message_on` отправить обычный запрос в личку и проверить, что бот ведёт его через одно редактируемое сообщение.
6. При `/single_message_off` отправить обычный запрос в личку и проверить, что бот снова шлёт отдельные сообщения по этапам и отдельный финальный ответ.
## Ожидаемый результат
- `/settings` стал основной точкой входа для пользовательских настроек.
- `/help` стал короче и не дублирует список команд настроек.
- Режим ответа в личке реально переключается персонально для пользователя и сохраняется после перезапуска сервиса.
## Статус
`pending`
@@ -1,210 +0,0 @@
# Shine Payments: e2e после переписи без Anchor и добавления Q3
## Краткое описание
Нужно вручную и через вспомогательные CLI-проверки подтвердить, что программа `shine_payments` после:
- переписи на чистый `solana_program`;
- отказа от `programs/common`;
- добавления очереди `Q3`;
- обновления HTML UI;
корректно работает на devnet с новым `program id`.
Отличие от финального боевого сценария:
- вместо DAO-механики используется обычный кошелёк `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`, которому даны права DAO на изменение коэффициента и выдачу лимитов менеджеру.
## Что именно проверять
### 1. Подготовка окружения
Проверить и зафиксировать:
- новый keypair программы `shine_payments`;
- новый `program id`;
- обновление `program id` в HTML UI и связанных настройках;
- наличие deploy authority, которой можно закрыть старый buffer/programdata, если это технически доступно;
- адреса тестовых кошельков:
- DAO/базовый кошелёк;
- менеджер;
- покупатель 1;
- покупатель 2;
- получатели выплат.
### 2. Очистка/смена старой программы
Проверить один из сценариев:
- если возможно, закрыть старый `program buffer/programdata` текущими ключами;
- если закрытие невозможно или нецелесообразно, зафиксировать это и продолжить с новым `program id`.
Отдельно проверить, что старые PDA предыдущей версии не используются новой программой.
### 3. Деплой и init новой программы
Проверить:
- `cargo build-sbf` проходит;
- новая программа деплоится на devnet;
- `init` выполняется один раз на пустых PDA;
- после `init` читаются:
- `config`;
- `coef_limit`;
- `queues`;
- `inflow_vault`.
Сразу после `init` запросить состояние очередей и зафиксировать, что:
- `Q1`, `Q2`, `Q3` пустые;
- `tickets_total = 0`;
- `tickets_paid = 0`;
- все суммы равны `0`.
### 4. Проверка покупки билета
На минимальных суммах проверить:
1. покупку через `buy_ticket_usd`;
2. покупку через `buy_ticket_sol`;
3. при необходимости ещё один вызов базового `buy_ticket`.
После каждой покупки:
- запросить состояние `Q1`;
- убедиться, что создался следующий ticket;
- проверить рост:
- `q1_tickets_total`;
- `q1_sum_total_usd_cents`;
- убедиться, что деньги покупки ушли в `dao_wallet`, а не в `inflow_vault`.
### 5. Проверка DAO-управления
Проверить:
1. изменение коэффициента через `update_coef_limit`;
2. повторный запрос `coef_limit` и подтверждение нового значения;
3. выдачу менеджеру прав через `grant_manager_limits`:
- отдельно под `Q1`;
- отдельно под `Q2`;
- отдельно под `Q3`.
После выдачи лимитов:
- считать `manager_allowance_pda`;
- убедиться, что лимиты записаны отдельно по трём очередям.
### 6. Проверка manager_add_ticket
На минимальных суммах создать менеджерские тикеты:
1. один ticket в `Q1`;
2. один ticket в `Q2`;
3. один ticket в `Q3`.
После каждого добавления:
- запросить состояние очередей;
- проверить рост счётчиков и сумм именно у нужной очереди;
- проверить уменьшение соответствующего manager allowance.
### 7. Проверка приоритета очередей
Подтвердить очередность `step_payout`:
1. сначала выплачивается `Q1`;
2. затем `Q2`;
3. затем `Q3`.
Для этого:
- между шагами регулярно читать `queues`;
- фиксировать, какой именно ticket был следующим к выплате;
- убедиться, что при наличии pending в `Q1` программа не уходит в `Q2` или `Q3`.
### 8. Проверка частичных выплат
Перед выплатами пополнять `inflow_vault` только минимально достаточными суммами.
Нужно проверить:
1. частичную серию выплат, когда часть тикетов ещё остаётся pending;
2. дополнительную покупку билета в промежутке между выплатами;
3. повторную проверку приоритета после появления нового билета в `Q1`.
После каждого `step_payout`:
- запрашивать состояние очередей;
- проверять:
- рост `tickets_paid`;
- рост `sum_paid_usd_cents`;
- `is_paid = true` у погашенного ticket;
- правильный DAO multiplier:
- `Q1 -> 1x`;
- `Q2 -> 2x`;
- `Q3 -> 3x`.
### 9. Проверка финального добора
После частичных выплат:
- купить ещё один билет;
- допополнить `inflow_vault`;
- выполнить оставшиеся `step_payout` до полного погашения всех трёх очередей.
В конце:
- все pending ticket должны отсутствовать;
- все суммы paid должны совпасть с total по каждой очереди;
- если вызвать `step_payout` на пустых очередях, доступный остаток `inflow_vault` должен уйти в `dao_wallet`.
### 10. Финальный возврат лампортов
После завершения теста вернуть все доступные остатки, которые можно вернуть текущими полномочиями, на базовый кошелёк:
- `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`
Отдельно зафиксировать:
- что именно удалось вернуть;
- что именно нельзя вернуть без специальной инструкции закрытия или без deploy authority.
## Ожидаемый результат
- `buy_ticket_usd` и `buy_ticket_sol` создают ticket без ошибок чтения state;
- `Q3` работает наравне с `Q2`, но с третьим приоритетом;
- DAO может менять коэффициент и выдавать лимиты;
- менеджер может создавать билеты во все три очереди;
- `step_payout` соблюдает порядок `Q1 -> Q2 -> Q3`;
- DAO-множитель на выплатах равен `1x/2x/3x` для `Q1/Q2/Q3`;
- HTML UI и on-chain программа используют один и тот же актуальный `program id`;
- остатки средств после теста по максимуму возвращены на базовый DAO-кошелёк.
## Статус
- `done`
## Итог выполнения
- новый `shine_payments` задеплоен в devnet с `program id`:
- `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW`
- старый `shine_payments`:
- `m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR`
- закрыт, лампорты возвращены на базовый DAO-кошелёк
- HTML UI переведён на новый `program id`
- подтверждены:
- `init`
- `buy_ticket_usd`
- `buy_ticket_sol`
- `grant_manager_limits`
- `manager_add_ticket` для `Q1/Q2/Q3`
- `change_ticket_recipient`
- `update_coef_limit`
- `step_payout` по порядку `Q1 -> Q2 -> Q3`
- повторный возврат приоритета в `Q1` после новой покупки
- итоговые агрегаты очередей:
- `Q1 total=4, paid=4, sum_total=780, sum_paid=780`
- `Q2 total=1, paid=1, sum_total=60, sum_paid=60`
- `Q3 total=1, paid=1, sum_total=70, sum_paid=70`
- временные тестовые кошельки собраны обратно в базовый DAO-кошелёк
- в `inflow_vault` остался только rent-минимум PDA
@@ -1,30 +0,0 @@
# Клиентская Solana-регистрация после ухода от Anchor
## Краткое описание
Исправлен рассинхрон обычного клиентского UI с no-Anchor ABI программ:
- `shine_login_guard`
- `shine_users`
Исправлены клиентские вызовы:
1. Solana-предпроверка логина в обычном UI.
2. `init_users_economy_config` в обычном UI.
## Что проверять
1. На странице регистрации проверка свободного логина не выдаёт `InvalidInstructionData`.
2. Для свободного обычного логина отображается корректный статус без fallback-предупреждения про недоступную Solana-предпроверку.
3. Регистрация пользователя через обычный UI проходит до конца.
4. Страница `Solana: init регистрации` в обычном UI отправляет корректную транзакцию и не падает из-за старого Anchor discriminator.
## Ожидаемый результат
1. `shine_login_guard` принимает клиентский precheck.
2. `init_users_economy_config` из обычного UI совместим с текущей программой `shine_users`.
3. Обычный клиентский UI ведёт себя так же, как серверный UI, там где используется общий no-Anchor путь.
## Статус
- `pending`
@@ -1,26 +0,0 @@
# ESP32 UI-прототип сабсервера SHiNE
- краткое описание фичи:
для `Waveshare ESP32-S3-Touch-AMOLED-2.16` добавлен новый интерактивный UI-скетч сабсервера `SHiNE` с хранением данных в `NVS`, настройками `Wi-Fi`, настройками серверов, кошельком, экраном `QR/URI`, живой Solana-регистрацией и экраном входящих запросов. Логика PIN в коде сохранена, но вход по PIN во временной сборке отключён, чтобы не блокировать проверку остальных экранов. В текущей версии `Wi-Fi` подключается реально, адреса `API/RPC/WS` проверяются реально, баланс кошелька читается из `Solana RPC`, а регистрация отправляет `create_user_pda` в `shine_users`.
- что именно проверять:
1. Прошить режим `subserver-ui` и дождаться открытия главного экрана без PIN.
2. Проверить, что текст в заголовках, кнопках и статусах отображается читаемо; в текущей временной сборке допускается ASCII-транслитерация русского текста.
3. Открыть `Настройки` и убедиться, что показывается пометка о временно отключённом входе по PIN.
4. Открыть `Подключение -> Wi-Fi`, ввести `SSID` и пароль, нажать `Проверить`, дождаться реального подключения, затем перезагрузить устройство и проверить, что значения сохранились.
5. Открыть `Подключение -> Серверы`, проверить или изменить `API/RPC/WS`, нажать `Проверить` и убедиться, что показываются реальные статусы доступности, затем перезагрузить устройство и проверить сохранение значений.
6. Открыть `Аккаунт`, ввести логин, имя сабсервера и нажать `Сгенерировать`; проверить, что появились секрет и адрес кошелька, а после перезагрузки они не исчезают.
7. Открыть `Кошелёк`, нажать `Проверить` и убедиться, что баланс реально читается из `Solana RPC`; затем открыть `QR и URI` и проверить, что QR-код отрисовывается и сканируется как `solana:`-ссылка.
8. При необходимости отдельно проверить тестовые кнопки `+/- SOL`: они меняют локальный баланс для UX-сценариев, но после следующей реальной RPC-проверки баланс должен вернуться к сетевому значению.
9. Вернуться на главный экран и проверить, что до выполнения всех условий кнопка регистрации недоступна, а после выполнения становится доступной.
10. Выполнить регистрацию и убедиться, что статус меняется на `Сабсервер активен`, онлайн-статус становится активным, а на экране появляются краткие отпечатки `PDA/TX`.
11. После регистрации проверить через `Solana`/UI проекта, что `user_pda` для этого логина реально создана и соответствует `device`-адресу устройства.
12. Открыть `Запросы`, поочерёдно открыть оба демонстрационных запроса и проверить, что кнопки `Разрешить` и `Отклонить` меняют их статус.
13. При необходимости открыть `Настройки -> Сменить PIN` и убедиться, что новый PIN сохраняется, хотя вход по PIN временно не используется на старте.
14. Выполнить `Полный сброс` и убедиться, что все поля, секрет, баланс, онлайн и регистрация очищаются.
- ожидаемый результат:
новый `ESP32`-скетч стабильно запускается, показывает читаемый интерфейс хотя бы в ASCII-транслитерации, сохраняет данные во внутренней памяти устройства, реально подключается к `Wi-Fi`, реально проверяет `API/RPC/WS`, реально читает баланс из `Solana RPC`, рисует рабочий `QR` для `solana:`-URI и позволяет вручную пройти полный сценарий on-chain регистрации сабсервера.
- статус:
pending
@@ -1,13 +0,0 @@
# ESP32 авто-прошивка shine_subserver_ui
- краткое описание фичи:
добавлен исполняемый скрипт `flash_shine_subserver_ui.sh`, который автоматически ищет USB-порт `ESP32` и запускает заливку прошивки `shine_subserver_ui` без ручного указания `PORT`.
- что именно проверять:
1. Подключить плату `ESP32` по USB.
2. Перейти в папку `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/`.
3. Запустить `./flash_shine_subserver_ui.sh`.
4. Убедиться, что скрипт сам показывает найденный порт и успешно запускает compile/upload.
- ожидаемый результат:
скрипт без ручного ввода порта находит `ESP32`, печатает найденный `/dev/ttyACM*` или `/dev/ttyUSB*` и заливает `shine_subserver_ui`.
- статус:
pending
@@ -1,14 +0,0 @@
# ESP32 тест рендера текста
- краткое описание фичи:
добавлен отдельный диагностический скетч `text_render_test`, который показывает один экран с несколькими вариантами вывода текста: встроенный шрифт `Arduino_GFX`, `U8g2` ASCII, `U8g2` кириллица и кнопки с подписями. Скрипт нужен для изоляции проблемы, когда на экране видны только цветные кнопки и блоки, но не видно ни одной буквы.
- что именно проверять:
1. Прошить режим `text-test`.
2. Проверить, виден ли заголовок `TEXT TEST 123`.
3. Проверить, видны ли строки `A`, `B`, `C`, `D`.
4. Проверить, видны ли подписи на трёх нижних кнопках: `BTN 1`, `abc123`, `Русский`.
5. Сравнить, какой из способов вывода реально отображается, а какой нет.
- ожидаемый результат:
хотя бы один вариант вывода текста становится видим на экране, что позволяет локализовать проблему до конкретного шрифта или способа рендера.
- статус:
pending
@@ -1,12 +0,0 @@
# ESP32 PIN-клавиатура: подписи кнопок
- краткое описание фичи:
в UI-скетче `shine_subserver_ui` изменена отрисовка подписей кнопок. Вместо малого шрифта теперь используется более стабильный шрифт с явным центрированием текста внутри кнопок, чтобы на экране ввода PIN и других экранах не пропадали цифры и надписи.
- что именно проверять:
1. Включить устройство и дождаться экрана ввода PIN.
2. Убедиться, что на всех серых кнопках видны цифры `0-9`, `Отмена` и `OK`.
3. Открыть другие экраны с кнопками (`Главный экран`, `Wi-Fi`, `Серверы`, `Настройки`) и убедиться, что подписи отображаются и не уезжают за границы кнопок.
- ожидаемый результат:
подписи кнопок стабильно видны сразу после старта, текст визуально центрирован, пустых серых кнопок без цифр и названий нет.
- статус:
pending
@@ -1,13 +0,0 @@
# ESP32 папка тестовых скетчей
- краткое описание фичи:
добавлена отдельная папка `test_sketches/` с изолированными диагностическими скетчами для экрана `ESP32-S3-Touch-AMOLED-2.16`: тест рендера текста через `Arduino_GFX`, тест геометрии кнопок и минимальный тест `LVGL`.
- что именно проверять:
1. Запустить `./burn.sh gfx-text-test` и убедиться, что прошивается тест текста из новой папки.
2. Запустить `./burn.sh gfx-layout-test` и проверить нижние ряды кнопок.
3. Запустить `./burn.sh lvgl-basic-test` и проверить, что `LVGL` показывает текст и кнопки.
4. Убедиться, что новая папка не мешает сборке `subserver-ui`.
- ожидаемый результат:
тестовые скетчи лежат отдельно от основного UI, шьются отдельными режимами и позволяют быстро проверять разные гипотезы по экрану без правок в `shine_subserver_ui`.
- статус:
pending
@@ -1,14 +0,0 @@
# ESP32 LVGL interaction test
- краткое описание фичи:
добавлен отдельный скетч `lvgl_interaction_test` на `LVGL`: экран с 9 кнопками, touch-вводом и нижней статусной строкой. При нажатии на кнопку на экране и в `Serial` показывается, какая именно кнопка нажата и сколько нажатий уже было.
- что именно проверять:
1. Прошить режим `lvgl-interaction-test`.
2. Убедиться, что виден заголовок, подзаголовок, 9 кнопок и нижняя статусная панель.
3. Поочерёдно нажать разные кнопки.
4. Проверить, что нижняя строка меняется на `Pressed: <button> (#N)`.
5. Проверить, что touch устойчиво работает по всей сетке кнопок.
- ожидаемый результат:
`LVGL` стабильно рисует плотный экран с множеством кнопок, а нажатия корректно обрабатываются и визуально подтверждаются без глюков позиционирования.
- статус:
pending
@@ -1,14 +0,0 @@
# ESP32 LVGL touch debug test
- краткое описание фичи:
добавлен отдельный диагностический скетч `lvgl_touch_debug_test`, который одновременно показывает сырые координаты touch, маркер точки касания и одну большую кнопку `LVGL`. Он нужен, чтобы отделить проблему raw-touch от проблемы доставки событий в `LVGL`.
- что именно проверять:
1. Прошить режим `lvgl-touch-debug-test`.
2. Коснуться экрана в разных местах.
3. Проверить, меняется ли текст `RAW pressed` и координаты `x/y`.
4. Проверить, появляется ли розовый маркер точки касания.
5. Проверить, срабатывает ли большая кнопка `Tap Here` и меняется ли строка `LVGL button clicked`.
- ожидаемый результат:
становится ясно, работает ли сам touch-драйвер, правильно ли приходят координаты и доходит ли нажатие до кнопки `LVGL`.
- статус:
pending
@@ -1,13 +0,0 @@
# ESP32 LVGL official based test
- краткое описание фичи:
добавлен отдельный скетч `lvgl_official_based_test`, который строится на максимально близкой к официальному `05_LVGL_Widgets` инициализации `display + touch + LVGL`, но вместо официального demo рисует наш компактный экран с кнопками и статусом нажатия.
- что именно проверять:
1. Прошить режим `lvgl-official-based-test`.
2. Убедиться, что экран отображается без артефактов по краям.
3. Нажать разные кнопки и проверить, меняется ли нижняя строка `Pressed: ...`.
4. Проверить, идут ли координаты touch в `Serial`.
- ожидаемый результат:
если официальный каркас инициализации действительно является рабочей базой, то на этом тесте должны заработать и touch, и кнопки, и исчезнуть визуальные артефакты, которые были в наших самодельных `LVGL`-тестах.
- статус:
pending
@@ -1,12 +0,0 @@
# LVGL Russian font test
- Краткое описание: тест кастомного `LVGL`-шрифта с кириллицей на базе рабочего `LVGL + subserver touch` контура.
- Что проверять:
- на экране видны русские заголовки и подписи без транслита;
- отображаются буквы `Ё/ё`;
- видны кнопки `Статус`, `Подключение`, `Кошелёк`, `Запросы`, `Настройки`, `Регистрация`, `Разрешить`, `Отклонить`, `Назад`;
- длинная кнопка `Проверка переноса русского текста` отображается читаемо;
- строка `Нажато:` меняется при клике;
- строка `Касание:` меняется при касании.
- Ожидаемый результат: кириллица стабильно отображается на `LVGL`-экране и не ломает touch.
- Статус: pending
@@ -1,72 +0,0 @@
# ESP32 nav minimal test
- Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста.
- Что проверять:
- стартует экран `HOME`;
- на `HOME` видны реальное значение сабсервера или `subserver not set`, реальное значение логина или `login not set`, при отсутствии секрета строка `secret not set`, а также `STATUS`, верхний правый блок с процентом батареи, иконкой батареи и индикатором Wi-Fi, кнопка `SETTINGS` и нижняя подпись `SHiNE subserver (v.0.18)`;
- строка Wi-Fi на `HOME` корректно показывает одно из состояний:
- `Wi-Fi (not configured) not configured`
- `Wi-Fi (<saved_ssid>) disconnected`
- `Wi-Fi (<current_ssid>) connected`
- пока открыт `HOME`, статус сам обновляется без перехода на другие экраны;
- кнопка `SETTINGS` открывает `SETTINGS_MENU`;
- свайп влево на `HOME` открывает `SETTINGS_MENU`;
- в `SETTINGS_MENU` сначала видны только `Wi-Fi` и `Server`;
- обе видимые карточки меню одного цвета;
- свайп вверх показывает `Server` и `Account`;
- свайп вниз возвращает `Wi-Fi` и `Server`;
- свайп вправо из `SETTINGS_MENU` возвращает на `HOME`;
- нажатие `Wi-Fi` открывает `WIFI_SCREEN`;
- `SELECT NETWORK` запускает скан;
- после скана показывается список доступных SSID;
- выбор SSID открывает общий экран редактирования текста для пароля;
- на этом экране видно старое значение, курсор стоит в конце;
- две верхние служебные строки над полем ввода отсутствуют;
- при вводе пароля Wi-Fi текст показывается открыто, без точек;
- большая клавиатура реально видна на экране и занимает большую часть высоты;
- буквы разбиты на 2 страницы;
- режим символов тоже разбит на 2 страницы;
- на правой странице кнопки стоят в ровных вертикальных колонках;
- свайп влево/вправо на экране ввода переключает страницы клавиатуры;
- при этом свайп страниц клавиатуры срабатывает только из нижней клавиатурной зоны, а не из верхней части экрана;
- при переключении `ABC/123` и `SHIFT` уже введённый текст не пропадает;
- при свайпе между левой и правой половиной клавиатуры уже введённый текст тоже не пропадает, в том числе для цифр, символов и заглавных букв;
- визуальный курсор в поле ввода не показывается;
- новые символы всегда дописываются только в конец строки;
- основные 3 ряда клавиш и нижний служебный ряд стали выше;
- внизу остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`, а рамка клавиатурного блока заканчивается выше неё;
- одно непрерывное касание вызывает не более одного действия кнопки;
- скольжение пальцем по клавиатуре не нажимает подряд несколько клавиш;
- медленный свайп по экрану не должен превращаться в случайное нажатие кнопки;
- `ABC/123`, `SHIFT`, `DEL`, `SAVE`, `CANCEL` работают;
- при успехе SSID и пароль сохраняются, а `HOME` показывает `Wi-Fi connected`;
- при ошибке показывается `Connection failed`;
- `CLEAR SAVED WI-FI` очищает сохранённые настройки;
- если сеть была ранее успешно сохранена, после потери связи устройство автоматически пытается переподключиться;
- первые повторные попытки идут раз в `10` секунд, а после долгого отсутствия связи интервал увеличивается до `30` секунд;
- нажатие `Server` открывает `SERVER_SCREEN`;
- в `SERVER_SCREEN` видны и редактируются два значения:
- `https://api.devnet.solana.com`
- `https://shineup.me`
- нажатие `SOLANA RPC` открывает общий экран редактирования;
- нажатие `SHINE SERVER` открывает общий экран редактирования;
- после `SAVE` новые адреса сохраняются в NVS;
- нажатие `Account` открывает `ACCOUNT_SCREEN`;
- `ACCOUNT_SCREEN` показывает 3 кнопки:
- `Login (<value|not set>)`
- `Subserver (<value|not set>)`
- `Secret (<*****|not set>)`
- `Login` открывает общий экран редактирования и сохраняется в NVS;
- `Subserver` открывает промежуточный экран с `USE SUBSERVER1` и `EDIT MANUALLY`;
- `USE SUBSERVER1` возвращает стандартное значение `subserver1`;
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
- `Secret` открывает экран-заглушку, где сказано, что настройка ещё не реализована;
- `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией;
- при смене `login` сохранённый секрет сбрасывается в `not set`;
- во время генерации секрета есть `CANCEL` и подтверждение остановки;
- при отмене генерации старый секрет, если он был, не должен теряться;
- свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`;
- свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`;
- если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`.
- Ожидаемый результат: новый скетч даёт чистый навигационный каркас и уже умеет настраивать Wi-Fi и серверные адреса на самой ESP32.
- Статус: pending
@@ -1,16 +0,0 @@
# Deeplink ссылки профиля и связей
- краткое описание:
Исправлена загрузка UI по прямым ссылкам вида `https://shineup.me/shine.<login>` и `https://shineup.me/shine.<login>/links` через добавление корневого `<base href="/">` в основной `index.html`.
- что проверять:
1. Открыть прямую ссылку на профиль в новой вкладке: `https://shineup.me/shine.<login>`.
2. Открыть прямую ссылку на связи в новой вкладке: `https://shineup.me/shine.<login>/links`.
3. Повторить оба сценария в состоянии гостя.
4. Повторить оба сценария в состоянии, когда в браузере залогинен другой пользователь.
- ожидаемый результат:
1. Страница загружается напрямую без поломки ассетов и без ухода на неверный экран.
2. Открывается профиль/связи именно пользователя из URL.
3. Для гостя экран открывается в read-only режиме.
4. Для залогиненного другого пользователя URL не подменяется на текущую сессию.
- статус:
pending
-269
View File
@@ -1,269 +0,0 @@
# Личные сообщения (DM): как это устроено
## Коротко (для быстрого понимания)
Личные сообщения в SHiNE сейчас работают как пара **подписанных клиентом блоков** в формате `SHiNE_dm2`:
- тип `1` — входящее сообщение для собеседника;
- тип `2` — исходящая копия того же сообщения для автора.
Оба блока отправляются вместе одной операцией (`SendMessagePair` / `ReceiveOutcomingMessage`) и либо сохраняются оба, либо не сохраняются вовсе.
Дальше сервер доставляет их по активным сессиям целевого логина событием `SignedMessageArrived`, а клиент подтверждает доставку на конкретную сессию через `AckSessionDelivery`.
Подтверждение прочтения также идёт парой блоков:
- тип `3` — «прочитано» для исходящего сообщения автора;
- тип `4` — зеркальная копия для второй стороны.
UI чата строится на этих типах: текстовые сообщения (1/2), read-receipt (3/4), непрочитанные, галочки и история.
---
## Подробно
## 1) Общая схема потока
1. Клиент формирует текст сообщения и строит **2 подписанных блока** (`type=1` и `type=2`) с одинаковыми `fromLogin/toLogin/timeMs/nonce`.
2. Клиент отправляет оба блока в одном RPC: `SendMessagePair` (алиас: `ReceiveOutcomingMessage`).
3. Сервер:
- парсит оба блока;
- валидирует пару;
- проверяет существование `from/to` пользователей и подписи;
- атомарно сохраняет пару в `signed_messages_v2`.
4. Сервер доставляет блоки в активные сессии целевого логина событием `SignedMessageArrived`.
5. Клиент, получив событие, кладёт сообщение в локальный чат и отправляет `AckSessionDelivery(messageKey)`.
6. При открытии чата клиент отправляет read-receipt (пара `type=3/4`) для непрочитанных входящих.
## 2) Формат signed DM-блока (`SHiNE_dm2`)
Префикс: `SHiNE_dm2` (ASCII).
Далее поля (big-endian):
1. `toLoginLen` (`u8`) + `toLogin` (ASCII, 1..60);
2. `fromLoginLen` (`u8`) + `fromLogin` (ASCII, 1..60);
3. `timeMs` (`u64`);
4. `nonce` (`u32`);
5. `messageType` (`u16`);
6. `payloadLen` (`u16`);
7. `payloadBytes` (`1..4096`);
8. `signature` (`64 bytes`, Ed25519).
Ограничения:
- полный пакет: до `8192` байт;
- `messageType` сейчас допустим только `1..4`.
## 3) Типы DM-сообщений
- `1` (`TYPE_INCOMING_TEXT`) — входящий текст для получателя.
- `2` (`TYPE_OUTGOING_COPY`) — исходящая копия в истории автора.
- `3` (`TYPE_READ_INCOMING`) — read-receipt (входящий тип для пары квитанции).
- `4` (`TYPE_READ_OUTGOING_COPY`) — зеркальная копия read-receipt.
Правило пары:
- первый блок должен быть нечётным (`1` или `3`);
- второй должен быть ровно `+1` (`2` или `4`);
- ключевые поля пары совпадают: `toLogin/fromLogin/timeMs/nonce`.
## 4) Ключи сообщений
- `baseKey = from|to|timeMs|nonce`
- `messageKey = baseKey|messageType`
Эти ключи используются:
- для дедупликации;
- для связи read-receipt с исходным сообщением;
- для ACK доставки по сессии.
## 5) RPC и события
## `SendMessagePair` (алиас `ReceiveOutcomingMessage`)
Запрос:
```json
{
"op": "SendMessagePair",
"requestId": "req-1",
"payload": {
"incomingBlobB64": "<base64 signed block type 1 or 3>",
"outgoingBlobB64": "<base64 signed block type 2 or 4>"
}
}
```
Успешный ответ:
```json
{
"op": "SendMessagePair",
"requestId": "req-1",
"status": 200,
"ok": true,
"payload": {
"baseKey": "from|to|time|nonce",
"incomingKey": "from|to|time|nonce|1",
"outgoingKey": "from|to|time|nonce|2",
"deliveredWsSessions": 2,
"deliveredWebPushSessions": 1
}
}
```
## `SignedMessageArrived` (server event)
Событие в сессию получателя содержит:
- `messageKey`, `baseKey`;
- `fromLogin`, `toLogin`, `targetLogin`;
- `messageType`, `timeMs`, `nonce`;
- `blobB64`;
- `backlog` (признак догрузки из очереди).
## `AckSessionDelivery`
Запрос:
```json
{
"op": "AckSessionDelivery",
"requestId": "ack-1",
"payload": {
"messageKey": "from|to|time|nonce|1"
}
}
```
Ответ: `status=200`, echo `messageKey`.
## 6) Хранение на сервере (SQLite)
Основные таблицы:
1. `signed_messages_v2` — сами DM-блоки типов `1/2/3/4`:
- `message_key` (PK),
- `base_key`,
- `target_login`,
- `from_login`, `to_login`,
- `time_ms`, `nonce`, `message_type`,
- `raw_block`,
- `source_api`, `origin_session_id`,
- `receipt_ref_base_key`, `receipt_ref_type`.
2. `signed_message_session_delivery` — доставка по сессиям:
- составной PK `(message_key, session_id)`,
- `delivered` (0/1),
- `delivered_at_ms`, `created_at_ms`.
Примечание: историческая таблица `signed_direct_messages_history` в БД присутствует как legacy-слой, но текущий рабочий поток DM v2 опирается на `signed_messages_v2` + `signed_message_session_delivery`.
## 7) Доставка и backlog
- При сохранении пары сервер пытается сразу доставить в онлайн-сессии.
- Для офлайн/недоступных сессий остаётся pending-запись доставки в таблице `signed_message_session_delivery`.
- При подключении сессии сервер автоматически вызывает `dispatchPendingForSession`:
- для новой сессии регистрирует все существующие сообщения адресата как «недоставленные»;
- отправляет **все** pending через WebSocket событием `SignedMessageArrived(backlog=true)`;
- лимита на количество сообщений нет — передаётся вся история без ограничений.
- Клиент дедублирует входящие через `knownMessageKeys`: если `messageKey` уже есть локально — игнорирует.
- После получения клиент отправляет `AckSessionDelivery`, чтобы отметить `delivered=1` в таблице доставки.
## 8) Read-receipt логика
Когда клиент открывает чат:
1. ищет входящие `messageType=1` без `readReceiptSent`;
2. для каждого отправляет read-receipt как пару `type=3/4`;
3. после успешной отправки помечает `readReceiptSent`.
Сервер для read-receipt хранит ссылку на исходное сообщение:
- `receipt_ref_base_key`;
- `receipt_ref_type`.
Есть уникальность, чтобы не плодить дубликаты receipt на один и тот же `baseKey` для одного `target_login`.
## 9) Логика UI-клиента
### Хранилище сообщений
- In-memory: `state.chats[chatId]` — массив сообщений по каждому диалогу.
- Персистентно: IndexedDB база `shine-ui-messages-v1`, object store `messages`, ключ `messageKey`.
- `chatId` для `type=1``fromLogin`, для `type=2``toLogin`.
### Жизненный цикл при старте/подключении
1. `hydrateMessagesFromStore()` — читает все сообщения из IndexedDB в `state.chats` (до WebSocket-соединения).
2. После установки WebSocket-сессии сервер присылает backlog (`SignedMessageArrived(backlog=true)`) для всех недоставленных сообщений.
3. Клиент дедублирует через `knownMessageKeys` — уже имеющиеся в IndexedDB игнорируются.
4. Новые сообщения в реальном времени приходят теми же WebSocket-событиями.
### Очистка при выходе и смене пользователя
- При любом логауте (`terminateCurrentSession`) IndexedDB с сообщениями **удаляется полностью**.
- При входе нового пользователя через QR — IndexedDB удаляется явно до вызова `terminateCurrentSession`.
- При входе нового пользователя через логин/пароль — IndexedDB удаляется в `registration-keys-view.js` прямо перед `authorizeSession()`.
- Это гарантирует: при любом способе входа старые сообщения предыдущего пользователя не попадут к следующему.
### UI-поведение
- непрочитанные считаются по `from='in' && unread=true`;
- доставка/прочтение исходящих:
- `firstTick` — сообщение принято сервером,
- `secondTick` — пришло подтверждение прочтения;
- при открытии диалога UI автопрокручивает ленту в самый низ;
- после отправки нового сообщения UI сразу прокручивает ленту вниз.
## 10) Синхронизация личных сообщений между серверами
Когда пользователи зарегистрированы на разных серверах SHiNE, серверы должны синхронизировать DM между собой.
### Общий принцип
- Сервер A получает DM-блок, адресованный пользователю на сервере B.
- Сервер A пересылает этот блок серверу B (межсерверный relay).
- Сервер B сохраняет блок и доставляет его в активные сессии получателя.
- Серверы, между которыми идёт синхронизация, задаются списком `sync_servers` в PDA пользователя-сервера.
### Что синхронизируется
- Все DM-блоки типов `1/2` (текстовые сообщения) и `3/4` (read-receipt).
- Синхронизация двусторонняя: оба сервера должны уметь принимать и пересылать блоки.
### Идемпотентность
- Блоки имеют уникальный `message_key` (`from|to|timeMs|nonce|type`).
- Повторная доставка одного и того же блока безопасна — дедупликация происходит по `message_key`.
### Статус реализации
Межсерверная синхронизация DM **пока не реализована**. Текущая версия работает только в рамках одного сервера. Это задача для следующего этапа.
---
## 11) Инварианты (обязательно соблюдать при доработках)
1. Пара блоков (1/2 или 3/4) должна оставаться атомарной.
2. `messageKey`/`baseKey` формат должен быть совместим с текущей логикой дедупликации и receipt.
3. Доставка должна оставаться **по сессиям** с явным `AckSessionDelivery`.
4. Read-receipt не должен отправляться многократно на один и тот же `baseKey`.
5. Любые изменения DM-логики в коде должны сразу отражаться в этом документе.
## 12) Ключевые файлы реализации
- UI:
- `shine-UI/js/services/auth-service.js`
- `shine-UI/js/app.js`
- `shine-UI/js/state.js`
- `shine-UI/js/pages/chat-view.js`
- Сервер:
- `shine-server-net-protocol/.../messages/SignedMessageBlock.java`
- `shine-server-net-protocol/.../messages/SignedMessagesCore.java`
- `shine-server-net-protocol/.../messages/Net_SendMessagePair_Handler.java`
- `shine-server-net-protocol/.../messages/SignedMessagesRealtime.java`
- `shine-server-net-protocol/.../messages/Net_AckSessionDelivery_Handler.java`
- БД:
- `shine-server-db/src/main/java/shine/db/DatabaseInitializer.java`
- `shine-server-db/src/main/java/shine/db/dao/SignedMessagesV2DAO.java`
@@ -1,42 +0,0 @@
# TODO: доработка персональных сообщений для агентов
Статус: отложено.
## Что хотели сделать
Добавить упрощённую маршрутизацию персональных сообщений через служебную инструкцию в начале текстового payload (внутри подписанного DM-блока), чтобы:
- отличать сообщения человеку от сообщений агенту;
- отличать сообщения от человека и от агента;
- скрывать в обычном UI сообщения, адресованные агенту (`target=agent`);
- поддержать сценарий «сообщения самому себе между своими клиентами/устройствами», где один клиент/агент пишет другому в рамках одного логина.
## Базовая идея формата (черновик)
Пример префикса:
```text
@shine:pm:v1 {"target":"agent","agentId":"assistant","author":"human"}
Текст сообщения...
```
Пример ответа агента:
```text
@shine:pm:v1 {"target":"user","author":"agent","agentId":"assistant","agentLabel":"My Bot"}
Ответ агента...
```
## Почему отложено
- нужно отдельно согласовать финальный формат инструкции;
- нужно определить строгие правила UI-фильтрации и fallback;
- нужно определить, нужен ли позднее отдельный серверный роутинг для agent-сессий.
## Что сделать при возвращении к задаче
1. Зафиксировать окончательный формат префикса и JSON-полей.
2. Описать правила парсинга/валидации (включая битые/неполные префиксы).
3. Добавить UI-логику показа/скрытия agent-сообщений.
4. Добавить маркировку «ответ агента» в диалоге.
5. Продумать режим self-chat (между своими клиентами/агентом) в рамках одного логина.
@@ -1,29 +0,0 @@
# Сервер `93.170.12.154` — резервный
- Пользователь: `player`
- Каталог SHiNE: `/home/player/SHiNE`
- UI исходник (после rsync): `/home/player/SHiNE/SHiNE-UI`
- UI публикация для Caddy: `/var/www/shine-ui`
- Сервер: `/home/player/SHiNE/SHiNE-server/shine-server.jar`
- Данные: `/home/player/SHiNE/SHiNE-server/data/`
- `shine.sqlite`
- `*.bch`
- Логи сервера: `/home/player/SHiNE/SHiNE-server/logs/app.log`
## Сервисы
- `shine-server.service` (systemd)
- `caddy.service` (systemd)
## Статус
- Резервный сервер для SHiNE.
- Основной прод-сервер: `shineup.me` (подключение через `player@shineup.me`, IP определяется через DNS).
## Caddy
- Конфиг: `/etc/caddy/Caddyfile`
- Настройки:
- `no-store/no-cache` заголовки;
- `try_files {path} /index.html` (SPA fallback);
- `reverse_proxy /ws* -> 127.0.0.1:7070`.
@@ -1,98 +0,0 @@
# Деплой и инициализация Solana-регистрации (две обязательные программы)
## Коротко
Для рабочей регистрации пользователя нужны **обе** программы:
1. `shine_users` — хранение и обновление `user_pda`, economy-конфиг, логика регистрации.
2. `shine_login_guard` — проверка/классификация логина (CPI из `shine_users`).
Если задеплоена только одна из них — регистрация неработоспособна.
## Актуальные адреса (devnet)
- `shine_users` (регистрация пользователей):
`FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm`
- `shine_login_guard`:
`3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo`
- `shine_payments`:
`c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW`
## Подтверждение деплоя
- Сеть: `https://api.devnet.solana.com`
- `shine_users`:
- `Program ID`: `FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm`
- TX deploy: `5VzfpSirFCRqPUZfvAt3eADY9KnowW79PKZ1pCQAa2DJGiztj4dUYYXrSQNmWEhPVu6mPSDfcuHzFyEVmoKLa9DM`
- `shine_login_guard`:
- `Program ID`: `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo`
- TX deploy: `5iptngPYrLLjPE3Xby24zyNW3edVUnBNLBx785vjojMoq5JNLFNQvLNAm3jNYHbpf2B36qtbpTNzcvUNyRDqm1Mf`
## Порядок деплоя (devnet)
1. Убедиться, что CLI смотрит в devnet и у кошелька есть SOL.
2. Собрать и задеплоить `shine_login_guard`.
3. Собрать и задеплоить `shine_users`.
4. Проверить, что адреса совпадают между:
- `Anchor.toml`
- `declare_id!` в `programs/*/src/lib.rs`
- UI/серверными константами.
5. Выполнить `init_users_economy_config` (один раз на программу `shine_users`).
Пример команд:
```bash
cd shine-solana/shine
solana config get
solana balance
anchor build -p shine_login_guard
anchor deploy -p shine_login_guard
anchor build -p shine_users
anchor deploy -p shine_users
```
## Куда вписаны адреса в проекте
### UI
- Общие Solana-константы:
- `shine-UI/js/solana-programs.js`
- Страница инициализации:
- `shine-UI/js/pages/solana-users-init-view.js`
- Переход на страницу:
- `shine-UI/js/pages/developer-settings-view.js`
### Сервер
- Серверные константы Solana:
- `shine-server-config/src/main/java/utils/config/SolanaProgramsConfig.java`
## Как запустить инициализацию economy PDA
1. Открыть UI.
2. Перейти: `Профиль -> Настройки -> Настройки разработчика -> Solana: init регистрации`.
3. Подключить кошелёк (Phantom, devnet).
4. Нажать `Запустить init_users_economy_config`.
5. Дождаться статуса `Успешно`.
Страница сама вычисляет PDA `users_economy_config` по seed:
- seed: `shine_users_economy_config`
- program: `FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm`
## Кто оплачивает create/update user_pda
- И обычная регистрация `create_user_pda`, и последующее `update_user_pda` оплачиваются с `deviceKey`.
- В UI это означает, что Solana fee payer всегда берётся из `device`-ключа пользователя/сервера.
- `rootKey` нужен для подписи unsigned PDA-записи, но не оплачивает транзакцию.
- Для server UI это особенно важно: перед `create` и `update` нужно пополнять именно Solana-адрес `deviceKey`.
## Важно
- `init_users_economy_config` выполняется один раз на программу.
Если PDA уже создан, повторный вызов вернёт ошибку "already initialized" (это нормальное поведение).
- Серверные приватные ключи для Solana не используются как отдельный backend-wallet: в UI/server UI транзакцию оплачивает именно `deviceKey`, а содержимое записи подписывает `rootKey`.
- `shine_users` внутри `create_user_pda` требует корректный адрес `shine_login_guard` для CPI-классификации логина.
Несовпадение адреса приведёт к ошибке регистрации.
+28
View File
@@ -0,0 +1,28 @@
# AGENTS for ESP32
## Язык UI
- Для ESP32-скетчей и экранного UI использовать английский язык.
- Русский текст на экране ESP32 пока не поддерживается корректно: шрифтовой путь для кириллицы не считается рабочим.
- Если меняется UI-скетч, все пользовательские строки на экране должны оставаться английскими, пока ограничение не снято отдельной задачей.
## Синхронизация со спецификацией
- При изменении экранов, кнопок, переходов, статусов или текстов обязательно обновлять соответствующую спецификацию в `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/`.
## Сборка ESP32
- Основной способ проверки и прошивки скетчей для `ESP32-S3-Touch-AMOLED-2.16` - `main-device/burn.sh`.
- Не собирать эти скетчи напрямую через `arduino-cli compile` без `burn.sh`, потому что скрипт добавляет нужные локальные библиотеки и конфиги из `official-demo/examples/Arduino-v3.3.5/libraries`.
- Если сборка падает по `lv_conf.h` или `TouchDrvCSTXXX.hpp`, сначала проверять именно `burn.sh` и его `--library` пути, а не считать, что файл пропал из репозитория.
## Диагностика ESP32
- Последнюю сохранённую ошибку или диагностическую запись читать с устройства через USB serial monitor на `115200`.
- Базовая команда:
`arduino-cli monitor -p /dev/ttyACM0 --config baudrate=115200`
- После подключения отправлять одну из команд:
`last_error`, `last_diag` или `reg_diag`
- Для очистки сохранённой диагностики использовать:
`clear_error` или `clear_diag`
- При падениях в ветках регистрации и обновления PDA сначала читать именно `last_error`: запись хранится в NVS и может пережить перезагрузку устройства.
+2 -2
View File
@@ -34,7 +34,7 @@ ls -l /dev/ttyACM0
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
- `original-firmware/` — backup/restore заводской прошивки
- `test-device/` — прошивки и `burn.sh`
- `main-device/` — прошивки и `burn.sh`
- `reference/` — заметки и ссылки
## 4) Бэкап перед любыми экспериментами
@@ -59,7 +59,7 @@ cd ESP32-S3-Touch-AMOLED-2.16/original-firmware
Главный скрипт:
```bash
cd ESP32-S3-Touch-AMOLED-2.16/test-device
cd ESP32-S3-Touch-AMOLED-2.16/main-device
./burn.sh <mode>
```
@@ -34,7 +34,7 @@ ls -l /dev/ttyACM0
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
- `original-firmware/` — backup/restore заводской прошивки
- `test-device/` — прошивки и `burn.sh`
- `main-device/` — прошивки и `burn.sh`
- `reference/` — заметки и ссылки
## 4) Бэкап перед любыми экспериментами
@@ -59,7 +59,7 @@ cd ESP32-S3-Touch-AMOLED-2.16/original-firmware
Главный скрипт:
```bash
cd ESP32-S3-Touch-AMOLED-2.16/test-device
cd ESP32-S3-Touch-AMOLED-2.16/main-device
./burn.sh <mode>
```
@@ -6,8 +6,9 @@
- `official-demo/` — официальный репозиторий примеров Waveshare
- `original-firmware/` — резервная копия заводской прошивки
- `test-device/` — скрипты быстрой проверки устройства
- `main-device/` — скрипты быстрой проверки устройства и основной скетч `shine_homeserver_main/`
- `reference/` — локальные заметки по документации и железу
- `main-device/shine_homeserver_main/` — основной рабочий скетч ESP32-проекта `SHiNE`
Примечание по git:
@@ -20,6 +21,8 @@
1. Сделать backup текущей прошивки:
- `cd original-firmware && ./backup_factory.sh`
2. Залить тест экрана/тача:
- `cd ../test-device && ./burn.sh widgets`
- `cd ../main-device && ./burn.sh widgets`
3. Залить тест динамика:
- `cd ../test-device && ./burn.sh audio`
- `cd ../main-device && ./burn.sh audio`
4. Залить основной UI:
- `cd ../main-device && ./burn.sh shine-homeserver-main`
@@ -1,6 +1,10 @@
# Test Device
# Main Device
Скрипт заливает официальные Arduino-примеры для быстрой проверки платы.
Основной скетч homeserver и старые тестовые скетчи для быстрой проверки платы.
`burn.sh` теперь:
- сам пытается найти USB-порт ESP32;
- сначала делает быструю инкрементальную сборку;
- если быстрая сборка не удалась, автоматически повторяет полную `clean`-сборку.
Для режимов `widgets`, `audio` и `hello` рядом должен лежать локальный checkout `official-demo/` из официального репозитория Waveshare. В основной git он не добавляется, потому что это большой внешний набор примеров, библиотек, прошивок и артефактов.
@@ -10,7 +14,10 @@
- `hello` — базовый тест экрана (пример `01_HelloWorld`)
- `simple` — простой кастомный тест: экран + touch + запись/проигрывание + наклон (IMU)
- `argon2` — генерация masterSecret через Argon2id с SD-картой как памятью (тест скорости)
- `subserver-ui`основной UI-прототип сабсервера SHiNE: NVS, PIN, Wi-Fi, серверы, кошелёк, QR, запросы
- `homeserver-ui`совместимый алиас, указывает на `shine_homeserver_main/`
- `shine-homeserver-main` — основной скетч проекта `SHiNE` для ESP32, текущая рабочая версия UI
- `shine-homeserver-ui-main` — старое имя основного скетча, оставлено как совместимый алиас
- `legacy-homeserver-ui` — старый UI-прототип `shine_homeserver_ui/`, оставлен как тестовый и не является основным
- `text-test` — диагностический экран рендера текста: default font, U8g2 ASCII, U8g2 кириллица, кнопки с подписями
- `gfx-text-test` — тот же тест рендера текста, но уже внутри новой папки `test_sketches/`
- `gfx-layout-test` — тест геометрии и нижних рядов кнопок
@@ -18,9 +25,9 @@
- `lvgl-interaction-test` — экран на `LVGL` с большим числом кнопок и сообщением о нажатой кнопке
- `lvgl-touch-debug-test` — точечная диагностика touch: сырые координаты, маркер точки и большая тест-кнопка `LVGL`
- `lvgl-official-based-test` — наш минимальный экран, но на максимально близкой к официальному `LVGL_Widgets` инициализации
- `lvgl-subserver-touch-test`гибрид: `LVGL`-интерфейс, но display/touch init и raw touch-read взяты из `shine_subserver_ui`; подтверждено на устройстве, touch работает, зелёных линий по краям нет
- `lvgl-subserver-touch-test`старый гибридный тест: `LVGL`-интерфейс, но display/touch init и raw touch-read взяты из старого `shine_homeserver_ui`; подтверждено на устройстве, touch работает, зелёных линий по краям нет
- `lvgl-russian-font-test` — тест кастомного `LVGL`-шрифта с кириллицей: русские кнопки, длинные подписи и статусы
- `lvgl-nav-minimal-test`новый минимальный UI-каркас сабсервера: `HOME`, `SETTINGS_MENU`, `Wi-Fi`, `Server`, `Account`, свайпы, крупные кнопки и реальная настройка Wi-Fi с сохранением в NVS
- `lvgl-nav-minimal-test`старое имя основного скетча, теперь ведёт на `shine_homeserver_main/` для совместимости
Запуск:
@@ -28,7 +35,10 @@
- `./burn.sh audio`
- `./burn.sh hello`
- `./burn.sh simple`
- `./burn.sh subserver-ui`
- `./burn.sh homeserver-ui`
- `./burn.sh shine-homeserver-main`
- `./burn.sh shine-homeserver-ui-main`
- `./burn.sh legacy-homeserver-ui`
- `./burn.sh text-test`
- `./burn.sh gfx-text-test`
- `./burn.sh gfx-layout-test`
@@ -39,4 +49,4 @@
- `./burn.sh lvgl-subserver-touch-test`
- `./burn.sh lvgl-russian-font-test`
- `./burn.sh lvgl-nav-minimal-test`
- `./flash_shine_subserver_ui.sh` - автоматически находит USB-порт и заливает `shine_subserver_ui`
- `./flash_shine_homeserver_main.sh` - автоматически находит USB-порт и заливает `shine_homeserver_main`
@@ -11,7 +11,7 @@
* legacy(empty password):
* secret = SHA256(base64(SHA256(password)) + "master.secret")
* keyPair_i = Ed25519(SHA256(base64(secret) + "|" + suffix_i))
* suffixes = ["root.key", "bch.key", "dev.key"]
* suffixes = ["root.key", "blockchain.key", "client.key"]
*
* Плата: Waveshare ESP32-S3-Touch-AMOLED-2.16
* SD : SDMMC 1-bit CLK=GPIO2, CMD=GPIO1, D0=GPIO3
@@ -116,8 +116,8 @@ static bool gKbNums = false;
// ═══════════════════════════════════════════════════════════
struct KeyPair { uint8_t pub[32]; uint8_t priv[32]; };
static KeyPair gKeys[3];
static const char * KEY_SUFFIXES[3] = {"root.key", "bch.key", "dev.key"};
static const char * KEY_LABELS[3] = {"root.key", "bch.key", "dev.key"};
static const char * KEY_SUFFIXES[3] = {"root.key", "blockchain.key", "client.key"};
static const char * KEY_LABELS[3] = {"root.key", "blockchain.key", "client.key"};
static uint32_t gElapsedSec = 0;
// Base58 представления (43-44 символа для 32 байт + \0)
@@ -5,18 +5,39 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BOARD_DIR="$(cd "${ROOT_DIR}/.." && pwd)"
DEMO_BASE="${BOARD_DIR}/official-demo/examples/Arduino-v3.3.5"
MODE="${1:-widgets}"
PORT="${PORT:-/dev/ttyACM0}"
PORT="${PORT:-}"
FQBN="${FQBN:-esp32:esp32:esp32s3:USBMode=hwcdc,CDCOnBoot=cdc,UploadSpeed=921600,CPUFreq=240,FlashMode=dio,FlashSize=16M,PartitionScheme=app3M_fat9M_16MB,PSRAM=opi}"
BUILD_DIR="${BUILD_DIR:-${ROOT_DIR}/.arduino-build/build-${MODE}}"
OUT_DIR="${OUT_DIR:-${ROOT_DIR}/.arduino-build/out-${MODE}}"
detect_port() {
local detected
detected="$(arduino-cli board list 2>/dev/null | awk '/\/dev\/tty(ACM|USB)/ {print $1; exit}')"
if [[ -n "${detected}" ]]; then
echo "${detected}"
return 0
fi
for candidate in /dev/ttyACM* /dev/ttyUSB*; do
if [[ -e "${candidate}" ]]; then
echo "${candidate}"
return 0
fi
done
return 1
}
case "${MODE}" in
hello) SKETCH_DIR="${DEMO_BASE}/examples/01_HelloWorld" ;;
widgets) SKETCH_DIR="${DEMO_BASE}/examples/05_LVGL_Widgets" ;;
audio) SKETCH_DIR="${DEMO_BASE}/examples/07_ES8311" ;;
simple) SKETCH_DIR="${ROOT_DIR}/simple_av_test" ;;
argon2) SKETCH_DIR="${ROOT_DIR}/argon2_sd_test" ;;
subserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_subserver_ui" ;;
homeserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
shine-homeserver-main) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
shine-homeserver-ui-main) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
legacy-homeserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_ui" ;;
text-test) SKETCH_DIR="${ROOT_DIR}/text_render_test" ;;
gfx-text-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_text_render_test" ;;
gfx-layout-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_button_layout_test" ;;
@@ -26,14 +47,21 @@ case "${MODE}" in
lvgl-official-based-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_official_based_test" ;;
lvgl-subserver-touch-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_subserver_touch_test" ;;
lvgl-russian-font-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_russian_font_test" ;;
lvgl-nav-minimal-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_nav_minimal_test" ;;
lvgl-nav-minimal-test) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
*)
echo "Unknown mode: ${MODE}" >&2
echo "Use one of: hello, widgets, audio, simple, argon2, subserver-ui, text-test, gfx-text-test, gfx-layout-test, lvgl-basic-test, lvgl-interaction-test, lvgl-touch-debug-test, lvgl-official-based-test, lvgl-subserver-touch-test, lvgl-russian-font-test, lvgl-nav-minimal-test" >&2
echo "Use one of: hello, widgets, audio, simple, argon2, homeserver-ui, shine-homeserver-main, shine-homeserver-ui-main, legacy-homeserver-ui, text-test, gfx-text-test, gfx-layout-test, lvgl-basic-test, lvgl-interaction-test, lvgl-touch-debug-test, lvgl-official-based-test, lvgl-subserver-touch-test, lvgl-russian-font-test" >&2
exit 2
;;
esac
if [[ -z "${PORT}" ]]; then
if ! PORT="$(detect_port)"; then
echo "Failed to auto-detect ESP32 port. Set PORT=/dev/ttyACM0 ./burn.sh ${MODE}" >&2
exit 3
fi
fi
echo "== Mode: ${MODE}"
echo "== Sketch: ${SKETCH_DIR}"
echo "== Port: ${PORT}"
@@ -41,17 +69,23 @@ echo "== FQBN: ${FQBN}"
mkdir -p "${BUILD_DIR}" "${OUT_DIR}"
arduino-cli compile \
--clean \
--fqbn "${FQBN}" \
--build-path "${BUILD_DIR}" \
--output-dir "${OUT_DIR}" \
--library "${DEMO_BASE}/libraries/GFX_Library_for_Arduino" \
--library "${DEMO_BASE}/libraries/SensorLib" \
--library "${DEMO_BASE}/libraries/XPowersLib" \
--library "${DEMO_BASE}/libraries/lvgl" \
--library "${DEMO_BASE}/libraries/Mylibrary" \
compile_args=(
--fqbn "${FQBN}"
--build-path "${BUILD_DIR}"
--output-dir "${OUT_DIR}"
--library "${DEMO_BASE}/libraries/GFX_Library_for_Arduino"
--library "${DEMO_BASE}/libraries/SensorLib"
--library "${DEMO_BASE}/libraries/XPowersLib"
--library "${DEMO_BASE}/libraries/lvgl"
--library "${DEMO_BASE}/libraries/Mylibrary"
"${SKETCH_DIR}"
)
echo "== Compile: fast incremental build"
if ! arduino-cli compile "${compile_args[@]}"; then
echo "== Compile: fast build failed, retrying clean build"
arduino-cli compile --clean "${compile_args[@]}"
fi
arduino-cli upload \
-p "${PORT}" \
@@ -43,9 +43,9 @@ fi
if [[ -z "${PORT}" ]]; then
echo "Не удалось автоматически найти USB-порт ESP32." >&2
echo "Подключите плату и проверьте 'arduino-cli board list'." >&2
echo "Либо укажите порт вручную: PORT=/dev/ttyACM0 ./flash_shine_subserver_ui.sh" >&2
echo "Либо укажите порт вручную: PORT=/dev/ttyACM0 ./flash_shine_homeserver_main.sh" >&2
exit 1
fi
echo "== Найден порт: ${PORT}"
PORT="${PORT}" "${ROOT_DIR}/burn.sh" subserver-ui
PORT="${PORT}" "${ROOT_DIR}/burn.sh" shine-homeserver-main
@@ -0,0 +1,14 @@
# SHiNE Homeserver UI Main
Это основной рабочий скетч ESP32-проекта `SHiNE`.
Текущая каноническая точка запуска:
- `./burn.sh shine-homeserver-main`
- `./burn.sh homeserver-ui`
Историческое имя этого скетча:
- `lvgl-nav-minimal-test`
Прежние тестовые варианты для этой платы остаются в `main-device/test_sketches/` и должны восприниматься как старые диагностические сборки, а не как основной UI.
@@ -1,7 +1,6 @@
#include "shine_secret_generation.h"
#include <SD_MMC.h>
#include <Preferences.h>
#include <mbedtls/sha256.h>
#include <mbedtls/base64.h>
#include <string.h>
@@ -80,6 +79,19 @@ static void setMessage(const char *message) {
snprintf(gMessage, sizeof(gMessage), "%s", message ? message : "");
}
static void sha256calc(const uint8_t *in, size_t len, uint8_t *out32);
static void finishSecretFromBytes(const uint8_t secret32[32], const char *message) {
memcpy(gSecret, secret32, 32);
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
gDone = true;
gRunning = false;
gError = false;
gInitDone = false;
gDoneBlocks = TOTAL_FILLS;
setMessage(message ? message : "Secret generated");
}
static void b2_compress(B2State *S, const uint8_t *blk) {
uint64_t m[16], v[16];
for (int i = 0; i < 16; i++) m[i] = ((const uint64_t *)blk)[i];
@@ -449,7 +461,6 @@ bool shineSecretInitSd(String &error) {
bool shineSecretStart(const char *normalizedLogin, const char *password, String &error) {
error = "";
if (!shineSecretInitSd(error)) return false;
if (!normalizedLogin || !*normalizedLogin) {
error = "login not set";
return false;
@@ -463,6 +474,8 @@ bool shineSecretStart(const char *normalizedLogin, const char *password, String
return false;
}
if (!shineSecretInitSd(error)) return false;
if (gSdFile) gSdFile.close();
SD_MMC.remove(SD_MEM_FILE);
gSdFile = SD_MMC.open(SD_MEM_FILE, "w+");
@@ -0,0 +1,6 @@
# SHiNE Homeserver UI Legacy
Это старый тестовый вариант UI для ESP32-платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Не использовать как основной скетч проекта.
Основной рабочий скетч сейчас лежит в `../shine_homeserver_main/`.
@@ -97,7 +97,7 @@ enum ActionId {
ACT_VERIFY_SERVERS,
ACT_SET_TEST_SERVERS,
ACT_EDIT_LOGIN,
ACT_EDIT_SUBSERVER,
ACT_EDIT_HOMESERVER,
ACT_GENERATE_SECRET,
ACT_CLEAR_ACCOUNT,
ACT_SHOW_QR,
@@ -137,7 +137,7 @@ enum EditTarget {
EDIT_SSID,
EDIT_WIFI_PASSWORD,
EDIT_LOGIN,
EDIT_SUBSERVER,
EDIT_HOMESERVER,
EDIT_API,
EDIT_RPC,
EDIT_WS,
@@ -174,7 +174,7 @@ struct AppData {
String wifiSsid;
String wifiPassword;
String login;
String subserverName;
String homeserverName;
String secret;
String walletAddress;
String userPdaAddress;
@@ -224,12 +224,14 @@ static int16_t gTouchLastY = 0;
struct DerivedKeyState {
bool ready;
uint8_t masterSecret[32];
uint8_t recoveryPub[32];
uint8_t recoverySk[64];
uint8_t rootPub[32];
uint8_t rootSk[64];
uint8_t blockchainPub[32];
uint8_t blockchainSk[64];
uint8_t devicePub[32];
uint8_t deviceSk[64];
uint8_t clientPub[32];
uint8_t clientSk[64];
};
static DerivedKeyState gDerivedKeys = {};
@@ -237,16 +239,16 @@ static DerivedKeyState gDerivedKeys = {};
static const char *kSystemProgramId = "11111111111111111111111111111111";
static const char *kEd25519ProgramId = "Ed25519SigVerify111111111111111111111111111";
static const char *kSysvarInstructionsId = "Sysvar1nstructions1111111111111111111111111";
static const char *kShineUsersProgramId = "FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm";
static const char *kShineLoginGuardProgramId = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo";
static const char *kShinePaymentsProgramId = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW";
static const char *kShineUsersProgramId = "SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6";
static const char *kShineLoginGuardProgramId = "SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh";
static const char *kShinePaymentsProgramId = "SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v";
static const char *kUsersSeedPrefix = "user_login=";
static const char *kUsersEconomyConfigSeed = "shine_users_economy_config";
static const char *kPaymentsInflowSeed = "shine_payments_inflow_vault";
static const char *kProgramDerivedAddressMarker = "ProgramDerivedAddress";
static const char *kLastBlockPrefix = "SHiNE_LAST_BLOCK";
static const uint8_t kBlockTypeRootKey = 1;
static const uint8_t kBlockTypeDeviceKey = 2;
static const uint8_t kBlockTypeClientKey = 2;
static const uint8_t kBlockTypeBlockchainRegistry = 3;
static const uint8_t kBlockTypeServerProfile = 30;
static const uint8_t kBlockTypeAccessServers = 40;
@@ -551,7 +553,7 @@ static bool canRegister() {
static String registrationSummary() {
if (gData.registered) {
return "Сабсервер активен";
return "Homeserver активен";
}
if (!gData.wifiReady) {
return "Нужен Wi-Fi";
@@ -782,19 +784,20 @@ static void pushFixed(std::vector<uint8_t> &out, const uint8_t *data, size_t len
static bool deriveKeysFromMasterSecret(const uint8_t masterSecret[32]) {
memset(&gDerivedKeys, 0, sizeof(gDerivedKeys));
memcpy(gDerivedKeys.masterSecret, masterSecret, 32);
String secretB64 = base64Encode(masterSecret, 32);
if (secretB64.length() == 0) {
return false;
}
const char *suffixes[3] = {"root.key", "bch.key", "dev.key"};
uint8_t *pubs[3] = {gDerivedKeys.rootPub, gDerivedKeys.blockchainPub, gDerivedKeys.devicePub};
uint8_t *sks[3] = {gDerivedKeys.rootSk, gDerivedKeys.blockchainSk, gDerivedKeys.deviceSk};
for (int i = 0; i < 3; i++) {
String material = secretB64 + "|" + suffixes[i];
const char *prefix = "SHiNE-key";
const char *suffixes[4] = {"recovery.key", "root.key", "blockchain.key", "client.key"};
uint8_t *pubs[4] = {gDerivedKeys.recoveryPub, gDerivedKeys.rootPub, gDerivedKeys.blockchainPub, gDerivedKeys.clientPub};
uint8_t *sks[4] = {gDerivedKeys.recoverySk, gDerivedKeys.rootSk, gDerivedKeys.blockchainSk, gDerivedKeys.clientSk};
for (int i = 0; i < 4; i++) {
std::vector<uint8_t> material;
material.reserve(strlen(prefix) + 1 + 32 + 1 + strlen(suffixes[i]));
material.insert(material.end(), prefix, prefix + strlen(prefix));
material.push_back(0);
material.insert(material.end(), masterSecret, masterSecret + 32);
material.push_back(0);
material.insert(material.end(), suffixes[i], suffixes[i] + strlen(suffixes[i]));
uint8_t seed[32];
if (!sha256String(material, seed)) {
return false;
}
sha256Raw(material.data(), material.size(), seed);
if (crypto_sign_seed_keypair(pubs[i], sks[i], seed) != 0) {
return false;
}
@@ -822,7 +825,7 @@ static bool restoreDerivedKeysFromSecret() {
return false;
}
gData.secretReady = true;
gData.walletAddress = bytesToBase58(gDerivedKeys.devicePub, 32);
gData.walletAddress = bytesToBase58(gDerivedKeys.clientPub, 32);
return true;
}
@@ -835,7 +838,7 @@ static bool deriveFreshSecretAndWallet() {
return false;
}
gData.secret = bytesToBase58(secret, sizeof(secret));
gData.walletAddress = bytesToBase58(gDerivedKeys.devicePub, 32);
gData.walletAddress = bytesToBase58(gDerivedKeys.clientPub, 32);
gData.secretReady = true;
return true;
}
@@ -889,7 +892,7 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
const String &blockchainName,
const String &serverAddress,
const uint8_t rootPub[32],
const uint8_t devicePub[32],
const uint8_t clientPub[32],
const uint8_t blockchainPub[32],
const uint8_t lastBlockSignature[64],
uint64_t createdAtMs) {
@@ -911,9 +914,9 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
out.push_back(0);
pushFixed(out, rootPub, 32);
out.push_back(kBlockTypeDeviceKey);
out.push_back(kBlockTypeClientKey);
out.push_back(0);
pushFixed(out, devicePub, 32);
pushFixed(out, clientPub, 32);
out.push_back(kBlockTypeBlockchainRegistry);
out.push_back(0);
@@ -960,7 +963,7 @@ static std::vector<uint8_t> buildCreateInstructionData(
const String &blockchainName,
const String &serverAddress,
const uint8_t rootPub[32],
const uint8_t devicePub[32],
const uint8_t clientPub[32],
const uint8_t blockchainPub[32],
const uint8_t lastBlockSignature[64],
const uint8_t rootSignature[64],
@@ -972,7 +975,7 @@ static std::vector<uint8_t> buildCreateInstructionData(
pushFixed(out, rootPub, 32);
pushU64LE(out, createdAtMs);
pushU64LE(out, 0);
pushFixed(out, devicePub, 32);
pushFixed(out, clientPub, 32);
pushFixed(out, blockchainPub, 32);
pushStrU8(out, blockchainName);
pushU64LE(out, 0);
@@ -1079,7 +1082,7 @@ static bool pdaAlreadyExists(const String &login, String &pdaAddress, String &me
static std::vector<uint8_t> buildLegacyMessage(
const uint8_t recentBlockhash[32],
const uint8_t devicePub[32],
const uint8_t clientPub[32],
const uint8_t userPda[32],
const uint8_t inflowVault[32],
const uint8_t economyConfig[32],
@@ -1098,7 +1101,7 @@ static std::vector<uint8_t> buildLegacyMessage(
base58ToFixed32(kShineLoginGuardProgramId, loginGuardProgram);
std::vector<std::vector<uint8_t>> accountKeys;
accountKeys.emplace_back(devicePub, devicePub + 32);
accountKeys.emplace_back(clientPub, clientPub + 32);
accountKeys.emplace_back(userPda, userPda + 32);
accountKeys.emplace_back(inflowVault, inflowVault + 32);
accountKeys.emplace_back(systemProgram, systemProgram + 32);
@@ -1179,7 +1182,7 @@ static bool awaitTransactionConfirmation(const String &signatureB58, String &mes
return false;
}
static bool registerSubserverOnSolana(String &messageOut) {
static bool registerHomeserverOnSolana(String &messageOut) {
messageOut = "";
if (!gDerivedKeys.ready) {
if (!restoreDerivedKeysFromSecret()) {
@@ -1244,7 +1247,7 @@ static bool registerSubserverOnSolana(String &messageOut) {
uint64_t createdAtMs = (uint64_t)millis() + 1704067200000ULL;
std::vector<uint8_t> unsignedRecord = buildUnsignedCreateRecord(
gData.login, blockchainName, gData.wsUrl,
gDerivedKeys.rootPub, gDerivedKeys.devicePub, gDerivedKeys.blockchainPub,
gDerivedKeys.rootPub, gDerivedKeys.clientPub, gDerivedKeys.blockchainPub,
lastBlockSignature, createdAtMs);
uint8_t unsignedHash[32];
uint8_t rootSignature[64];
@@ -1256,7 +1259,7 @@ static bool registerSubserverOnSolana(String &messageOut) {
std::vector<uint8_t> createData = buildCreateInstructionData(
gData.login, blockchainName, gData.wsUrl,
gDerivedKeys.rootPub, gDerivedKeys.devicePub, gDerivedKeys.blockchainPub,
gDerivedKeys.rootPub, gDerivedKeys.clientPub, gDerivedKeys.blockchainPub,
lastBlockSignature, rootSignature, createdAtMs);
std::vector<uint8_t> edRootData = buildEd25519InstructionData(rootSignature, gDerivedKeys.rootPub, unsignedHash);
std::vector<uint8_t> edBchData = buildEd25519InstructionData(lastBlockSignature, gDerivedKeys.blockchainPub, lastBlockHash);
@@ -1269,7 +1272,7 @@ static bool registerSubserverOnSolana(String &messageOut) {
std::vector<uint8_t> message = buildLegacyMessage(
recentBlockhash,
gDerivedKeys.devicePub,
gDerivedKeys.clientPub,
userPda,
inflowVault,
economyConfig,
@@ -1277,7 +1280,7 @@ static bool registerSubserverOnSolana(String &messageOut) {
edBchData,
createData);
uint8_t txSignature[64];
if (!signMessageEd25519(message, gDerivedKeys.deviceSk, txSignature)) {
if (!signMessageEd25519(message, gDerivedKeys.clientSk, txSignature)) {
messageOut = "Не удалось подписать Solana-транзакцию";
return false;
}
@@ -1311,7 +1314,7 @@ static String defaultApiUrl() {
}
static String defaultRpcUrl() {
return "https://api.devnet.solana.com";
return "https://api.mainnet-beta.solana.com";
}
static String defaultWsUrl() {
@@ -1656,7 +1659,7 @@ static bool refreshWalletBalance(String &messageOut) {
static void seedRequests() {
gRequests[0].type = "Вход в сессию";
gRequests[0].actor = "Chrome / aidarkc";
gRequests[0].details = "Клиент просит подключиться к сабсерверу и открыть сессию без ввода пароля.";
gRequests[0].details = "Клиент просит подключиться к homeserverу и открыть сессию без ввода пароля.";
gRequests[0].status = "Ожидает";
gRequests[1].type = "Подпись сообщения";
@@ -1670,7 +1673,7 @@ static void loadDefaults() {
gData.wifiSsid = "";
gData.wifiPassword = "";
gData.login = "";
gData.subserverName = "subserver1";
gData.homeserverName = "homeserver1";
gData.secret = "";
gData.walletAddress = "";
gData.userPdaAddress = "";
@@ -1692,7 +1695,7 @@ static void saveData() {
gPrefs.putString("wifi_ssid", gData.wifiSsid);
gPrefs.putString("wifi_pass", gData.wifiPassword);
gPrefs.putString("login", gData.login);
gPrefs.putString("subserver", gData.subserverName);
gPrefs.putString("homeserver", gData.homeserverName);
gPrefs.putString("secret", gData.secret);
gPrefs.putString("wallet", gData.walletAddress);
gPrefs.putString("user_pda", gData.userPdaAddress);
@@ -1714,7 +1717,7 @@ static void loadData() {
gData.wifiSsid = gPrefs.getString("wifi_ssid", gData.wifiSsid);
gData.wifiPassword = gPrefs.getString("wifi_pass", gData.wifiPassword);
gData.login = gPrefs.getString("login", gData.login);
gData.subserverName = gPrefs.getString("subserver", gData.subserverName);
gData.homeserverName = gPrefs.getString("homeserver", gData.homeserverName);
gData.secret = gPrefs.getString("secret", gData.secret);
gData.walletAddress = gPrefs.getString("wallet", gData.walletAddress);
gData.userPdaAddress = gPrefs.getString("user_pda", gData.userPdaAddress);
@@ -1758,8 +1761,8 @@ static void generateSecretAndWallet() {
gData.registrationSignature = "";
gData.registered = false;
gData.online = false;
if (gData.subserverName.length() == 0) {
gData.subserverName = "subserver1";
if (gData.homeserverName.length() == 0) {
gData.homeserverName = "homeserver1";
}
saveData();
}
@@ -1815,7 +1818,7 @@ static String editTargetLabel() {
case EDIT_SSID: return "SSID";
case EDIT_WIFI_PASSWORD: return "Пароль Wi-Fi";
case EDIT_LOGIN: return "Логин";
case EDIT_SUBSERVER: return "Имя сабсервера";
case EDIT_HOMESERVER: return "Имя homeserver";
case EDIT_API: return "API URL";
case EDIT_RPC: return "RPC URL";
case EDIT_WS: return "WS URL";
@@ -1846,7 +1849,7 @@ static void drawHomeScreen() {
drawPanel(20, 92, 440, 98, C_PANEL, C_BORDER, 16);
drawText(36, 122, registrationSummary(), canRegister() || gData.registered ? C_ACCENT : C_WARN, (const uint8_t *)FONT_HEAD);
drawText(36, 152, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT, (const uint8_t *)FONT_BODY);
drawText(36, 174, "Сабсервер: " + gData.subserverName, C_MUTE, (const uint8_t *)FONT_BODY);
drawText(36, 174, "Homeserver: " + gData.homeserverName, C_MUTE, (const uint8_t *)FONT_BODY);
drawPanel(20, 204, 210, 82, C_CARD, C_BORDER, 12);
drawText(34, 232, "Wi-Fi", C_TEXT, (const uint8_t *)FONT_BODY);
@@ -1871,7 +1874,7 @@ static void drawStatusScreen() {
drawTopBar("Статус");
drawPanel(20, 92, 440, 286, C_PANEL, C_BORDER, 16);
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
drawText(36, 148, "Сабсервер: " + gData.subserverName, C_TEXT);
drawText(36, 148, "Homeserver: " + gData.homeserverName, C_TEXT);
drawText(36, 174, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
drawText(36, 200, "Отпечаток: " + (gData.secretReady ? shortenValue(gData.secret) : "-"), C_MUTE, (const uint8_t *)FONT_SMALL);
drawText(36, 226, "Wi-Fi: " + boolText(gData.wifiReady, "готов", "не готов"), gData.wifiReady ? C_ACCENT : C_WARN);
@@ -1947,13 +1950,13 @@ static void drawAccountScreen() {
drawTopBar("Аккаунт");
drawPanel(20, 92, 440, 188, C_PANEL, C_BORDER, 16);
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
drawText(36, 152, "Сабсервер: " + gData.subserverName, C_TEXT);
drawText(36, 152, "Homeserver: " + gData.homeserverName, C_TEXT);
drawText(36, 182, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
drawText(36, 212, "Кошелёк: " + (gData.walletAddress.length() ? shortenValue(gData.walletAddress, 10, 8) : "не создан"), C_MUTE, (const uint8_t *)FONT_SMALL);
drawText(36, 236, "Регистрация: " + boolText(gData.registered, "выполнена", "не выполнена"), gData.registered ? C_ACCENT : C_WARN);
drawText(36, 260, "PDA: " + registrationDetailsShort(), C_MUTE, (const uint8_t *)FONT_SMALL);
addButton(20, 300, 212, 48, ACT_EDIT_LOGIN, "Изменить логин");
addButton(248, 300, 212, 48, ACT_EDIT_SUBSERVER, "Имя сабсервера");
addButton(248, 300, 212, 48, ACT_EDIT_HOMESERVER, "Имя homeserver");
addButton(20, 360, 212, 48, ACT_GENERATE_SECRET, "Сгенерировать", true, C_OK);
addButton(248, 360, 212, 48, ACT_CLEAR_ACCOUNT, "Очистить", true, C_BUTTON2);
addButton(20, 420, 440, 36, ACT_BACK, "Назад");
@@ -2107,7 +2110,7 @@ static void drawConfirmScreen() {
String text = "Выполнить действие?";
if (gConfirmTarget == CONFIRM_REGISTER) {
title = "Регистрация";
text = "Отправить create_user_pda в Solana через device key этого устройства?";
text = "Отправить create_user_pda в Solana через client key этого устройства?";
} else if (gConfirmTarget == CONFIRM_CLEAR_ACCOUNT) {
title = "Очистка";
text = "Удалить секрет, кошелёк и статус регистрации?";
@@ -2193,9 +2196,9 @@ static void applyEditValue() {
gData.registrationSignature = "";
gNotice = "Логин сохранён";
break;
case EDIT_SUBSERVER:
gData.subserverName = value.length() ? value : "subserver1";
gNotice = "Имя сабсервера сохранено";
case EDIT_HOMESERVER:
gData.homeserverName = value.length() ? value : "homeserver1";
gNotice = "Имя homeserver сохранено";
break;
case EDIT_API:
gData.apiUrl = value;
@@ -2351,7 +2354,7 @@ static void handleAction(ActionId action) {
}
if (action == ACT_CONFIRM_YES) {
if (gConfirmTarget == CONFIRM_REGISTER) {
registerSubserverOnSolana(gNotice);
registerHomeserverOnSolana(gNotice);
} else if (gConfirmTarget == CONFIRM_CLEAR_ACCOUNT) {
gData.secret = "";
gData.walletAddress = "";
@@ -2445,7 +2448,7 @@ static void handleAction(ActionId action) {
gNeedRedraw = true;
break;
case ACT_EDIT_LOGIN: openEdit(EDIT_LOGIN, gData.login, false); break;
case ACT_EDIT_SUBSERVER: openEdit(EDIT_SUBSERVER, gData.subserverName, false); break;
case ACT_EDIT_HOMESERVER: openEdit(EDIT_HOMESERVER, gData.homeserverName, false); break;
case ACT_GENERATE_SECRET:
generateSecretAndWallet();
gNotice = gData.secretReady ? "Секрет сгенерирован, device-кошелёк выведен из него" : "Не удалось сгенерировать секрет";
@@ -1,8 +1,9 @@
# Test Sketches
Набор отдельных диагностических скетчей для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Набор старых отдельных диагностических скетчей для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Скетчи в этой папке нужны для быстрой проверки конкретных гипотез без влияния на основной `shine_subserver_ui`.
Скетчи в этой папке нужны для быстрой проверки конкретных гипотез и не являются основным UI проекта.
Основной скетч сейчас лежит в `main-device/shine_homeserver_main/`.
## Список
@@ -12,9 +13,9 @@
- `lvgl_interaction_test/` - расширенный тест `LVGL` с 9 кнопками, touch-вводом и статусом нажатия
- `lvgl_touch_debug_test/` - диагностика touch: сырые координаты, точка касания и одна большая кнопка `LVGL`
- `lvgl_official_based_test/` - минимальный наш экран поверх максимально близкой к официальному `05_LVGL_Widgets` инициализации
- `lvgl_subserver_touch_test/` - гибридный тест: `LVGL`-экран с инициализацией дисплея и чтением touch из `shine_subserver_ui`; подтверждён на реальном устройстве
- `lvgl_subserver_touch_test/` - старый гибридный тест: `LVGL`-экран с инициализацией дисплея и чтением touch из старого `shine_homeserver_ui`; подтверждён на реальном устройстве
- `lvgl_russian_font_test/` - тест кастомного кириллического `LVGL`-шрифта с русскими кнопками, длинными строками и рабочим touch
- `lvgl_nav_minimal_test/` - новый минимальный навигационный каркас сабсервера на рабочем `LVGL + subserver touch`, расширенный настройкой Wi-Fi и сохранением в NVS
- `lvgl_nav_minimal_test/` - старое тестовое имя, этот скетч перенесён в `shine_homeserver_main/` и теперь является основным
## Запуск
@@ -28,4 +29,3 @@
- `./burn.sh lvgl-official-based-test`
- `./burn.sh lvgl-subserver-touch-test`
- `./burn.sh lvgl-russian-font-test`
- `./burn.sh lvgl-nav-minimal-test`
@@ -4,7 +4,7 @@
#include <Arduino_GFX_Library.h>
#include <TouchDrvCSTXXX.hpp>
// Подтверждено на устройстве: LVGL-рендер работает вместе с touch-путём из shine_subserver_ui.
// Подтверждено на устройстве: LVGL-рендер работает вместе с touch-путём из shine_homeserver_ui.
#define PIN_LCD_CS 12
#define PIN_LCD_SCLK 38
@@ -146,7 +146,7 @@ static void createUi() {
lv_obj_align(gVersionLabel, LV_ALIGN_TOP_MID, 0, 12);
lv_obj_t *subtitle = lv_label_create(screen);
lv_label_set_text(subtitle, "Touch path comes from shine_subserver_ui. Tap buttons and watch status.");
lv_label_set_text(subtitle, "Touch path comes from shine_homeserver_ui. Tap buttons and watch status.");
lv_obj_set_width(subtitle, 436);
lv_label_set_long_mode(subtitle, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_14, 0);
@@ -0,0 +1,784 @@
/**
* @file lv_conf.h
* Configuration file for v8.4.0
*/
/*
* Copy this file as `lv_conf.h`
* 1. simply next to the `lvgl` folder
* 2. or any other places and
* - define `LV_CONF_INCLUDE_SIMPLE`
* - add the path as include path
*/
/* clang-format off */
#if 1 /*Set it to "1" to enable content*/
#ifndef LV_CONF_H
#define LV_CONF_H
#include <stdint.h>
/*====================
COLOR SETTINGS
*====================*/
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 0
/*Enable features to draw on transparent background.
*It's required if opa, and transform_* style properties are used.
*Can be also used if the UI is above another layer, e.g. an OSD menu or video player.*/
#define LV_COLOR_SCREEN_TRANSP 0
/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
* 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
#define LV_COLOR_MIX_ROUND_OFS 0
/*Images pixels with this color will not be drawn if they are chroma keyed)*/
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/
/*=========================
MEMORY SETTINGS
*=========================*/
/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
#define LV_MEM_CUSTOM 0
#if LV_MEM_CUSTOM == 0
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
#define LV_MEM_SIZE (48U * 1024U) /*[bytes]*/
/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
#define LV_MEM_ADR 0 /*0: unused*/
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
#if LV_MEM_ADR == 0
#undef LV_MEM_POOL_INCLUDE
#undef LV_MEM_POOL_ALLOC
#endif
#else /*LV_MEM_CUSTOM*/
#define LV_MEM_CUSTOM_INCLUDE <stdlib.h> /*Header for the dynamic memory function*/
#define LV_MEM_CUSTOM_ALLOC malloc
#define LV_MEM_CUSTOM_FREE free
#define LV_MEM_CUSTOM_REALLOC realloc
#endif /*LV_MEM_CUSTOM*/
/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
*You will see an error log message if there wasn't enough buffers. */
#define LV_MEM_BUF_MAX_NUM 16
/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
#define LV_MEMCPY_MEMSET_STD 0
/*====================
HAL SETTINGS
*====================*/
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 10 /*[ms]*/
/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 10 /*[ms]*/
/*Use a custom tick source that tells the elapsed time in milliseconds.
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 0
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
/*If using lvgl as ESP32 component*/
// #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
// #define LV_TICK_CUSTOM_SYS_TIME_EXPR ((esp_timer_get_time() / 1000LL))
#endif /*LV_TICK_CUSTOM*/
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
*(Not so important, you can adjust it to modify default sizes and spaces)*/
#define LV_DPI_DEF 130 /*[px/inch]*/
/*=======================
* FEATURE CONFIGURATION
*=======================*/
/*-------------
* Drawing
*-----------*/
/*Enable complex draw engine.
*Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
#define LV_DRAW_COMPLEX 1
#if LV_DRAW_COMPLEX != 0
/*Allow buffering some shadow calculation.
*LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
*Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
#define LV_SHADOW_CACHE_SIZE 0
/* Set number of maximally cached circle data.
* The circumference of 1/4 circle are saved for anti-aliasing
* radius * 4 bytes are used per circle (the most often used radiuses are saved)
* 0: to disable caching */
#define LV_CIRCLE_CACHE_SIZE 4
#endif /*LV_DRAW_COMPLEX*/
/**
* "Simple layers" are used when a widget has `style_opa < 255` to buffer the widget into a layer
* and blend it as an image with the given opacity.
* Note that `bg_opa`, `text_opa` etc don't require buffering into layer)
* The widget can be buffered in smaller chunks to avoid using large buffers.
*
* - LV_LAYER_SIMPLE_BUF_SIZE: [bytes] the optimal target buffer size. LVGL will try to allocate it
* - LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE: [bytes] used if `LV_LAYER_SIMPLE_BUF_SIZE` couldn't be allocated.
*
* Both buffer sizes are in bytes.
* "Transformed layers" (where transform_angle/zoom properties are used) use larger buffers
* and can't be drawn in chunks. So these settings affects only widgets with opacity.
*/
#define LV_LAYER_SIMPLE_BUF_SIZE (24 * 1024)
#define LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE (3 * 1024)
/*Default image cache size. Image caching keeps the images opened.
*If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
*With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
*However the opened images might consume additional RAM.
*0: to disable caching*/
#define LV_IMG_CACHE_DEF_SIZE 0
/*Number of stops allowed per gradient. Increase this to allow more stops.
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
#define LV_GRADIENT_MAX_STOPS 2
/*Default gradient buffer size.
*When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again.
*LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes.
*If the cache is too small the map will be allocated only while it's required for the drawing.
*0 mean no caching.*/
#define LV_GRAD_CACHE_DEF_SIZE 0
/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display)
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
#define LV_DITHER_GRADIENT 0
#if LV_DITHER_GRADIENT
/*Add support for error diffusion dithering.
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
*The increase in memory consumption is (24 bits * object's width)*/
#define LV_DITHER_ERROR_DIFFUSION 0
#endif
/*Maximum buffer size to allocate for rotation.
*Only used if software rotation is enabled in the display driver.*/
#define LV_DISP_ROT_MAX_BUF (10*1024)
/*-------------
* GPU
*-----------*/
/*Use Arm's 2D acceleration library Arm-2D */
#define LV_USE_GPU_ARM2D 0
/*Use STM32's DMA2D (aka Chrom Art) GPU*/
#define LV_USE_GPU_STM32_DMA2D 0
#if LV_USE_GPU_STM32_DMA2D
/*Must be defined to include path of CMSIS header of target processor
e.g. "stm32f7xx.h" or "stm32f4xx.h"*/
#define LV_GPU_DMA2D_CMSIS_INCLUDE
#endif
/*Enable RA6M3 G2D GPU*/
#define LV_USE_GPU_RA6M3_G2D 0
#if LV_USE_GPU_RA6M3_G2D
/*include path of target processor
e.g. "hal_data.h"*/
#define LV_GPU_RA6M3_G2D_INCLUDE "hal_data.h"
#endif
/*Use SWM341's DMA2D GPU*/
#define LV_USE_GPU_SWM341_DMA2D 0
#if LV_USE_GPU_SWM341_DMA2D
#define LV_GPU_SWM341_DMA2D_INCLUDE "SWM341.h"
#endif
/*Use NXP's PXP GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_PXP 0
#if LV_USE_GPU_NXP_PXP
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
*/
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
#endif
/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_VG_LITE 0
/*Use SDL renderer API*/
#define LV_USE_GPU_SDL 0
#if LV_USE_GPU_SDL
#define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
/*Texture cache size, 8MB by default*/
#define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
#define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
#endif
/*-------------
* Logging
*-----------*/
/*Enable the log module*/
#define LV_USE_LOG 0
#if LV_USE_LOG
/*How important log should be added:
*LV_LOG_LEVEL_TRACE A lot of logs to give detailed information
*LV_LOG_LEVEL_INFO Log important events
*LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem
*LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail
*LV_LOG_LEVEL_USER Only logs added by the user
*LV_LOG_LEVEL_NONE Do not log anything*/
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
/*1: Print the log with 'printf';
*0: User need to register a callback with `lv_log_register_print_cb()`*/
#define LV_LOG_PRINTF 0
/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
#define LV_LOG_TRACE_MEM 1
#define LV_LOG_TRACE_TIMER 1
#define LV_LOG_TRACE_INDEV 1
#define LV_LOG_TRACE_DISP_REFR 1
#define LV_LOG_TRACE_EVENT 1
#define LV_LOG_TRACE_OBJ_CREATE 1
#define LV_LOG_TRACE_LAYOUT 1
#define LV_LOG_TRACE_ANIM 1
#endif /*LV_USE_LOG*/
/*-------------
* Asserts
*-----------*/
/*Enable asserts if an operation is failed or an invalid data is found.
*If LV_USE_LOG is enabled an error message will be printed on failure*/
#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/
#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
#define LV_USE_ASSERT_STYLE 0 /*Check if the styles are properly initialized. (Very fast, recommended)*/
#define LV_USE_ASSERT_MEM_INTEGRITY 0 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
#define LV_USE_ASSERT_OBJ 0 /*Check the object's type and existence (e.g. not deleted). (Slow)*/
/*Add a custom handler when assert happens e.g. to restart the MCU*/
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
/*-------------
* Others
*-----------*/
/*1: Show CPU usage and FPS count*/
#define LV_USE_PERF_MONITOR 0
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif
/*1: Show the used memory and the memory fragmentation
* Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 0
#if LV_USE_MEM_MONITOR
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif
/*1: Draw random colored rectangles over the redrawn areas*/
#define LV_USE_REFR_DEBUG 0
/*Change the built in (v)snprintf functions*/
#define LV_SPRINTF_CUSTOM 0
#if LV_SPRINTF_CUSTOM
#define LV_SPRINTF_INCLUDE <stdio.h>
#define lv_snprintf snprintf
#define lv_vsnprintf vsnprintf
#else /*LV_SPRINTF_CUSTOM*/
#define LV_SPRINTF_USE_FLOAT 0
#endif /*LV_SPRINTF_CUSTOM*/
#define LV_USE_USER_DATA 1
/*Garbage Collector settings
*Used if lvgl is bound to higher level language and the memory is managed by that language*/
#define LV_ENABLE_GC 0
#if LV_ENABLE_GC != 0
#define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/
#endif /*LV_ENABLE_GC*/
/*=====================
* COMPILER SETTINGS
*====================*/
/*For big endian systems set to 1*/
#define LV_BIG_ENDIAN_SYSTEM 0
/*Define a custom attribute to `lv_tick_inc` function*/
#define LV_ATTRIBUTE_TICK_INC
/*Define a custom attribute to `lv_timer_handler` function*/
#define LV_ATTRIBUTE_TIMER_HANDLER
/*Define a custom attribute to `lv_disp_flush_ready` function*/
#define LV_ATTRIBUTE_FLUSH_READY
/*Required alignment size for buffers*/
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1
/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
* E.g. __attribute__((aligned(4)))*/
#define LV_ATTRIBUTE_MEM_ALIGN
/*Attribute to mark large constant arrays for example font's bitmaps*/
#define LV_ATTRIBUTE_LARGE_CONST
/*Compiler prefix for a big array declaration in RAM*/
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY
/*Place performance critical functions into a faster memory (e.g RAM)*/
#define LV_ATTRIBUTE_FAST_MEM
/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
#define LV_ATTRIBUTE_DMA
/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
*should also appear on LVGL binding API such as Micropython.*/
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/
/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
#define LV_USE_LARGE_COORD 0
/*==================
* FONT USAGE
*===================*/
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
*https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8 1
#define LV_FONT_MONTSERRAT_10 1
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_18 1
#define LV_FONT_MONTSERRAT_20 1
#define LV_FONT_MONTSERRAT_22 1
#define LV_FONT_MONTSERRAT_24 1
#define LV_FONT_MONTSERRAT_26 1
#define LV_FONT_MONTSERRAT_28 1
#define LV_FONT_MONTSERRAT_30 1
#define LV_FONT_MONTSERRAT_32 1
#define LV_FONT_MONTSERRAT_34 1
#define LV_FONT_MONTSERRAT_36 1
#define LV_FONT_MONTSERRAT_38 1
#define LV_FONT_MONTSERRAT_40 1
#define LV_FONT_MONTSERRAT_42 1
#define LV_FONT_MONTSERRAT_44 1
#define LV_FONT_MONTSERRAT_46 1
#define LV_FONT_MONTSERRAT_48 1
/*Demonstrate special features*/
#define LV_FONT_MONTSERRAT_12_SUBPX 0
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Persian letters and all their forms*/
#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/
/*Pixel perfect monospace fonts*/
#define LV_FONT_UNSCII_8 0
#define LV_FONT_UNSCII_16 0
/*Optionally declare custom fonts here.
*You can use these fonts as default font too and they will be available globally.
*E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
#define LV_FONT_CUSTOM_DECLARE
/*Always set a default font*/
#define LV_FONT_DEFAULT &lv_font_montserrat_14
/*Enable handling large font and/or fonts with a lot of characters.
*The limit depends on the font size, font face and bpp.
*Compiler error will be triggered if a font needs it.*/
#define LV_FONT_FMT_TXT_LARGE 0
/*Enables/disables support for compressed fonts.*/
#define LV_USE_FONT_COMPRESSED 0
/*Enable subpixel rendering*/
#define LV_USE_FONT_SUBPX 0
#if LV_USE_FONT_SUBPX
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/
#endif
/*Enable drawing placeholders when glyph dsc is not found*/
#define LV_USE_FONT_PLACEHOLDER 1
/*=================
* TEXT SETTINGS
*=================*/
/**
* Select a character encoding for strings.
* Your IDE or editor should have the same character encoding
* - LV_TXT_ENC_UTF8
* - LV_TXT_ENC_ASCII
*/
#define LV_TXT_ENC LV_TXT_ENC_UTF8
/*Can break (wrap) texts on these chars*/
#define LV_TXT_BREAK_CHARS " ,.;:-_"
/*If a word is at least this long, will break wherever "prettiest"
*To disable, set to a value <= 0*/
#define LV_TXT_LINE_BREAK_LONG_LEN 0
/*Minimum number of characters in a long word to put on a line before a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3
/*Minimum number of characters in a long word to put on a line after a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3
/*The control character to use for signalling text recoloring.*/
#define LV_TXT_COLOR_CMD "#"
/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
*The direction will be processed according to the Unicode Bidirectional Algorithm:
*https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
#define LV_USE_BIDI 0
#if LV_USE_BIDI
/*Set the default direction. Supported values:
*`LV_BASE_DIR_LTR` Left-to-Right
*`LV_BASE_DIR_RTL` Right-to-Left
*`LV_BASE_DIR_AUTO` detect texts base direction*/
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
#endif
/*Enable Arabic/Persian processing
*In these languages characters should be replaced with an other form based on their position in the text*/
#define LV_USE_ARABIC_PERSIAN_CHARS 0
/*==================
* WIDGET USAGE
*================*/
/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/
#define LV_USE_ARC 1
#define LV_USE_BAR 1
#define LV_USE_BTN 1
#define LV_USE_BTNMATRIX 1
#define LV_USE_CANVAS 1
#define LV_USE_CHECKBOX 1
#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/
#define LV_USE_IMG 1 /*Requires: lv_label*/
#define LV_USE_LABEL 1
#if LV_USE_LABEL
#define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/
#define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/
#endif
#define LV_USE_LINE 1
#define LV_USE_ROLLER 1 /*Requires: lv_label*/
#if LV_USE_ROLLER
#define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/
#endif
#define LV_USE_SLIDER 1 /*Requires: lv_bar*/
#define LV_USE_SWITCH 1
#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/
#if LV_USE_TEXTAREA != 0
#define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
#endif
#define LV_USE_TABLE 1
/*==================
* EXTRA COMPONENTS
*==================*/
/*-----------
* Widgets
*----------*/
#define LV_USE_ANIMIMG 1
#define LV_USE_CALENDAR 1
#if LV_USE_CALENDAR
#define LV_CALENDAR_WEEK_STARTS_MONDAY 0
#if LV_CALENDAR_WEEK_STARTS_MONDAY
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
#else
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
#endif
#define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
#define LV_USE_CALENDAR_HEADER_ARROW 1
#define LV_USE_CALENDAR_HEADER_DROPDOWN 1
#endif /*LV_USE_CALENDAR*/
#define LV_USE_CHART 1
#define LV_USE_COLORWHEEL 1
#define LV_USE_IMGBTN 1
#define LV_USE_KEYBOARD 1
#define LV_USE_LED 1
#define LV_USE_LIST 1
#define LV_USE_MENU 1
#define LV_USE_METER 1
#define LV_USE_MSGBOX 1
#define LV_USE_SPAN 1
#if LV_USE_SPAN
/*A line text can contain maximum num of span descriptor */
#define LV_SPAN_SNIPPET_STACK_SIZE 64
#endif
#define LV_USE_SPINBOX 1
#define LV_USE_SPINNER 1
#define LV_USE_TABVIEW 1
#define LV_USE_TILEVIEW 1
#define LV_USE_WIN 1
/*-----------
* Themes
*----------*/
/*A simple, impressive and very complete theme*/
#define LV_USE_THEME_DEFAULT 1
#if LV_USE_THEME_DEFAULT
/*0: Light mode; 1: Dark mode*/
#define LV_THEME_DEFAULT_DARK 0
/*1: Enable grow on press*/
#define LV_THEME_DEFAULT_GROW 1
/*Default transition time in [ms]*/
#define LV_THEME_DEFAULT_TRANSITION_TIME 80
#endif /*LV_USE_THEME_DEFAULT*/
/*A very simple theme that is a good starting point for a custom theme*/
#define LV_USE_THEME_BASIC 1
/*A theme designed for monochrome displays*/
#define LV_USE_THEME_MONO 1
/*-----------
* Layouts
*----------*/
/*A layout similar to Flexbox in CSS.*/
#define LV_USE_FLEX 1
/*A layout similar to Grid in CSS.*/
#define LV_USE_GRID 1
/*---------------------
* 3rd party libraries
*--------------------*/
/*File system interfaces for common APIs */
/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 0
#if LV_USE_FS_STDIO
#define LV_FS_STDIO_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for open, read, etc*/
#define LV_USE_FS_POSIX 0
#if LV_USE_FS_POSIX
#define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for CreateFile, ReadFile, etc*/
#define LV_USE_FS_WIN32 0
#if LV_USE_FS_WIN32
#define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/
#define LV_USE_FS_FATFS 0
#if LV_USE_FS_FATFS
#define LV_FS_FATFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/
#define LV_USE_FS_LITTLEFS 0
#if LV_USE_FS_LITTLEFS
#define LV_FS_LITTLEFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_LITTLEFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*PNG decoder library*/
#define LV_USE_PNG 0
/*BMP decoder library*/
#define LV_USE_BMP 0
/* JPG + split JPG decoder library.
* Split JPG is a custom format optimized for embedded systems. */
#define LV_USE_SJPG 0
/*GIF decoder library*/
#define LV_USE_GIF 0
/*QR code library*/
#define LV_USE_QRCODE 1
/*FreeType library*/
#define LV_USE_FREETYPE 0
#if LV_USE_FREETYPE
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
#define LV_FREETYPE_CACHE_SIZE (16 * 1024)
#if LV_FREETYPE_CACHE_SIZE >= 0
/* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */
/* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */
/* if font size >= 256, must be configured as image cache */
#define LV_FREETYPE_SBIT_CACHE 0
/* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */
/* (0:use system defaults) */
#define LV_FREETYPE_CACHE_FT_FACES 0
#define LV_FREETYPE_CACHE_FT_SIZES 0
#endif
#endif
/*Tiny TTF library*/
#define LV_USE_TINY_TTF 0
#if LV_USE_TINY_TTF
/*Load TTF data from files*/
#define LV_TINY_TTF_FILE_SUPPORT 0
#endif
/*Rlottie library*/
#define LV_USE_RLOTTIE 0
/*FFmpeg library for image decoding and playing videos
*Supports all major image formats so do not enable other image decoder with it*/
#define LV_USE_FFMPEG 0
#if LV_USE_FFMPEG
/*Dump input information to stderr*/
#define LV_FFMPEG_DUMP_FORMAT 0
#endif
/*-----------
* Others
*----------*/
/*1: Enable API to take snapshot for object*/
#define LV_USE_SNAPSHOT 0
/*1: Enable Monkey test*/
#define LV_USE_MONKEY 0
/*1: Enable grid navigation*/
#define LV_USE_GRIDNAV 0
/*1: Enable lv_obj fragment*/
#define LV_USE_FRAGMENT 0
/*1: Support using images as font in label or span widgets */
#define LV_USE_IMGFONT 0
/*1: Enable a published subscriber based messaging system */
#define LV_USE_MSG 0
/*1: Enable Pinyin input method*/
/*Requires: lv_keyboard*/
#define LV_USE_IME_PINYIN 0
#if LV_USE_IME_PINYIN
/*1: Use default thesaurus*/
/*If you do not use the default thesaurus, be sure to use `lv_ime_pinyin` after setting the thesauruss*/
#define LV_IME_PINYIN_USE_DEFAULT_DICT 1
/*Set the maximum number of candidate panels that can be displayed*/
/*This needs to be adjusted according to the size of the screen*/
#define LV_IME_PINYIN_CAND_TEXT_NUM 6
/*Use 9 key input(k9)*/
#define LV_IME_PINYIN_USE_K9_MODE 1
#if LV_IME_PINYIN_USE_K9_MODE == 1
#define LV_IME_PINYIN_K9_CAND_TEXT_NUM 3
#endif // LV_IME_PINYIN_USE_K9_MODE
#endif
/*==================
* EXAMPLES
*==================*/
/*Enable the examples to be built with the library*/
#define LV_BUILD_EXAMPLES 1
/*===================
* DEMO USAGE
====================*/
/*Show some widget. It might be required to increase `LV_MEM_SIZE` */
#define LV_USE_DEMO_WIDGETS 1
#if LV_USE_DEMO_WIDGETS
#define LV_DEMO_WIDGETS_SLIDESHOW 1
#endif
/*Demonstrate the usage of encoder and keyboard*/
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK 1
#if LV_USE_DEMO_BENCHMARK
/*Use RGB565A8 images with 16 bit color depth instead of ARGB8565*/
#define LV_DEMO_BENCHMARK_RGB565A8 0
#endif
/*Stress test for LVGL*/
#define LV_USE_DEMO_STRESS 1
/*Music player demo*/
#define LV_USE_DEMO_MUSIC 1
#if LV_USE_DEMO_MUSIC
#define LV_DEMO_MUSIC_SQUARE 0
#define LV_DEMO_MUSIC_LANDSCAPE 0
#define LV_DEMO_MUSIC_ROUND 0
#define LV_DEMO_MUSIC_LARGE 0
#define LV_DEMO_MUSIC_AUTO_PLAY 0
#endif
/*--END OF LV_CONF_H--*/
#endif /*LV_CONF_H*/
#endif /*End of "Content enable"*/
@@ -1,39 +1,46 @@
# SHiNE ESP32 Subserver UI Nav Minimal Spec
# SHiNE ESP32 Homeserver UI Nav Minimal Spec
Минимальный навигационный прототип для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Легаси-спецификация старого навигационного теста для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Актуальный основной скетч теперь находится в `../main-device/shine_homeserver_main/`; этот документ оставлен только как историческая справка по старому тестовому UI.
## Цель
Этот прототип проверяет базовую механику экранов, крупных кнопок, свайпов, первичную настройку Wi-Fi и настройку серверных адресов через общий экран редактирования текста.
На этом этапе отсутствуют:
- логика серверной проверки доступности;
- логин/пароль учётной записи SHiNE;
- PIN;
- кошелёк;
- QR;
- баланс;
- регистрация;
- PDA и транзакции;
- PDA update/create транзакции;
- входящие запросы.
## Экраны
Прототип содержит 8 экранов:
Прототип содержит 10 экранов:
- `HOME`
- `SETTINGS_MENU`
- `WIFI_SCREEN`
- `SERVER_SCREEN`
- `ACCOUNT_SCREEN`
- `ACCOUNT_SUBSERVER_SCREEN`
- `ACCOUNT_HOMESERVER_SCREEN`
- `ACCOUNT_SECRET_SCREEN`
- `SECRET_SHOW_SCREEN`
- `SECRET_GENERATE_*`
- `TEXT_EDIT_SCREEN`
- `REGISTER_ACCOUNT_PLACEHOLDER`
## HOME
Показывает:
- сверху слева значение сабсервера или `subserver not set`;
- сверху слева значение homeserver или `homeserver not set`;
- ниже значение логина или `login not set`;
- справа от строки логина индикатор статуса Solana-аккаунта:
- зелёный — все ключи совпадают;
- красный — есть mismatch;
- белый контур — пользователь не найден в Solana PDA;
- рядом с индикатором краткий текст ошибки, если статус не зелёный;
- третьей строкой `secret not set`, если секрет ещё не помечен как установленный;
- сверху справа один ряд индикаторов:
- процент батареи;
@@ -41,8 +48,11 @@
- индикатор Wi-Fi уровня сигнала;
- по центру крупный текст `STATUS`;
- одна строка Wi-Fi вида `Wi-Fi (<ssid>) connected/disconnected`;
- снизу большую кнопку `SETTINGS`.
- внизу на тёмной полосе подпись `SHiNE subserver (v.0.18)`.
- кнопка баланса вида `Balance: <value SOL>` или `Balance: failed to load`, по нажатию выполняет повторный запрос;
- строка `SHiNE: <server> connected/account not configured/unavailable`;
- при отсутствии пользователя в Solana PDA слева снизу появляется кнопка `REGISTER ACCOUNT`;
- снизу кнопку `SETTINGS`, уменьшенную примерно до половины ширины экрана и сдвинутую к правому краю.
- внизу на тёмной полосе подпись `SHiNE homeserver (v.0.18)`.
Строка Wi-Fi на `HOME`:
- `Wi-Fi (not configured) not configured`
@@ -50,9 +60,23 @@
- `Wi-Fi (<current_ssid>) connected`
Переходы:
- кнопка `REGISTER ACCOUNT` -> `REGISTER_ACCOUNT_PLACEHOLDER`, только если пользователь не найден;
- кнопка `SETTINGS` -> `SETTINGS_MENU`;
- свайп влево -> `SETTINGS_MENU`.
Примечание:
- поведение `REGISTER ACCOUNT -> REGISTER_ACCOUNT_PLACEHOLDER` относится к старой тестовой версии и не является актуальным для основного скетча.
Фоновая логика:
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду;
- при наличии `login + secret + homeserver` и Wi-Fi устройство читает Solana `user_pda` напрямую через RPC;
- сравниваются `root key`, `blockchain key`, `client key` и `homeserver` session-запись типа `100`;
- для строки `SHiNE:` устройство держит отдельную WebSocket-сессию с сервером SHiNE:
- авторизация через `AuthChallenge/CreateAuthSession` или `SessionChallenge/SessionLogin`;
- session key = публичный `homeserver key`;
- подтверждение создания сессии подписывается `client key`;
- heartbeat выполняется `Ping` раз в минуту.
## SETTINGS_MENU
Показывает вертикальное меню из 3 пунктов:
@@ -90,7 +114,7 @@
Показывает:
- текущий Wi-Fi статус;
- сохранённый SSID;
- сохранённый SSID и число известных сетей;
- статусное сообщение;
- кнопку `SELECT NETWORK`;
- кнопку `CLEAR SAVED WI-FI`;
@@ -109,6 +133,13 @@
Нажатие на SSID открывает `TEXT_EDIT_SCREEN` для ввода пароля.
Поведение:
- если для выбранного SSID пароль уже был сохранён раньше, он сразу подставляется в поле ввода;
- если пароль меняют для другого SSID, старые сохранённые пароли других сетей не теряются;
- после успешного подключения выбранная сеть становится текущей `wifi_ssid/wifi_pass`;
- одновременно хранится до `8` известных сетей `SSID -> password`;
- `CLEAR SAVED WI-FI` очищает все сохранённые сети и текущую сеть.
Переходы:
- свайп вправо из любого режима `WIFI_SCREEN` -> `SETTINGS_MENU`
- кнопка `BACK` -> `SETTINGS_MENU`
@@ -119,12 +150,12 @@
- статусное сообщение;
- текущий `Solana RPC` адрес;
- кнопку `SOLANA RPC`;
- текущий `Shine server` адрес;
- кнопку `SHINE SERVER`.
- текущий `SHiNE server login` или уже резолвленный адрес;
- кнопку `SHiNE SERVER LOGIN`, если обычный `user PDA` ещё не зарегистрирован.
Значения по умолчанию:
- Solana RPC: `https://api.devnet.solana.com`
- Shine server: `https://shineup.me`
- Solana RPC: `https://api.mainnet-beta.solana.com`
- SHiNE server login: `shineupme`
Нажатие на любую из двух кнопок открывает `TEXT_EDIT_SCREEN`.
@@ -137,41 +168,59 @@
- заголовок `ACCOUNT`;
- статусное сообщение;
- кнопку `Login (<value|not set>)`;
- кнопку `Subserver (<value|not set>)`;
- кнопку `Homeserver (<value|not set>)`;
- кнопку `Secret (<*****|not set>)`.
Переходы:
- свайп вправо -> `SETTINGS_MENU`
- `Login` -> `TEXT_EDIT_SCREEN`
- `Subserver` -> `ACCOUNT_SUBSERVER_SCREEN`
- `Homeserver` -> `ACCOUNT_HOMESERVER_SCREEN`
- `Secret` -> `ACCOUNT_SECRET_SCREEN`
## ACCOUNT_SUBSERVER_SCREEN
## ACCOUNT_HOMESERVER_SCREEN
Показывает:
- текущий `subserver`;
- рекомендацию оставить `subserver1`, если устройство одно;
- кнопку `USE SUBSERVER1`;
- текущий `homeserver`;
- рекомендацию оставить `homeserver1`, если устройство одно;
- кнопку `USE HOMESERVER1`;
- кнопку `EDIT MANUALLY`;
- кнопку `BACK`.
Переходы:
- `USE SUBSERVER1` -> сохраняет `subserver1` и возвращает в `ACCOUNT_SCREEN`
- `USE HOMESERVER1` -> сохраняет `homeserver1` и возвращает в `ACCOUNT_SCREEN`
- `EDIT MANUALLY` -> `TEXT_EDIT_SCREEN`
- свайп вправо -> `ACCOUNT_SCREEN`
## ACCOUNT_SECRET_SCREEN
Пока это заглушка.
Показывает:
- текущий статус секрета `set/not set`;
- сообщение, что настройка секрета пока не реализована;
- кнопку `BACK`.
- кнопку `SHOW SECRET` или серую `SECRET NOT SET`, если секрета ещё нет;
- кнопку `ENTER SECRET MANUALLY (NOT RECOMMENDED)`;
- кнопку `GENERATE SECRET`.
Переходы:
- свайп вправо -> `ACCOUNT_SCREEN`
- `BACK` -> `ACCOUNT_SCREEN`
- `SHOW SECRET` -> экран просмотра секрета
- `ENTER SECRET MANUALLY (NOT RECOMMENDED)` -> `TEXT_EDIT_SCREEN`
- `GENERATE SECRET` -> экран подтверждения генерации
## SECRET_SHOW_SCREEN
Показывает:
- заголовок `SECRET`;
- вертикально прокручиваемый список ключей;
- `Secret (base58)`;
- `Root key (base58)`;
- `Root key priv (base58)`;
- `Blockchain key (base58)`;
- `Blockchain key priv (base58)`;
- `Client key (base58)`;
- `Client key priv (base58)`;
- `Homeserver key (base58)`;
- `Homeserver key priv (base58)`;
- для каждого поля показывается формула derivation;
- значения ключей показываются полными строками увеличенным шрифтом;
- кнопку `BACK`.
## TEXT_EDIT_SCREEN
@@ -180,7 +229,7 @@
Используется для:
- пароля Wi-Fi;
- Solana RPC;
- Shine server.
- SHiNE server login.
Показывает:
- заголовок;
@@ -191,6 +240,15 @@
- кнопки `SAVE`, `CANCEL`, `DEL`, `CLR`;
- большую экранную клавиатуру.
## REGISTER_ACCOUNT_PLACEHOLDER
Временный экран-заглушка регистрации.
Показывает:
- заголовок `REGISTER ACCOUNT`;
- сообщение, что регистрационный flow пока не реализован;
- кнопку `BACK`.
## Клавиатура
Клавиатура единая для всех текстовых вводов.
@@ -201,6 +259,7 @@
- переключение страниц выполняется свайпом влево/вправо внутри `TEXT_EDIT_SCREEN`;
- страница 1 содержит в основном буквы и базовые URL-символы;
- страница 2 содержит цифры и дополнительные URL/символьные кнопки;
- на символьных страницах должны быть доступны как минимум `!` и отдельная кнопка `SPACE`;
- переключение `ABC/123` и `SHIFT` не должно очищать уже введённый текст;
- переключение страниц клавиатуры свайпом тоже не должно очищать уже введённый текст, включая цифры, символы и уже набранные заглавные буквы;
- есть специальные действия `DEL` и `CLR`.
@@ -211,8 +270,13 @@
- `wifi_ssid`
- `wifi_pass`
- `wifi_known_good`
- до `8` сохранённых пар `SSID -> password`
При старте устройства, если сохранён SSID, выполняется попытка подключения к сохранённой сети.
При старте устройства, если сохранён SSID, выполняется попытка подключения к текущей сохранённой сети.
Если пользователь уже вводил пароль для сети раньше:
- при повторном выборе этого SSID старый пароль сразу подставляется в editor screen;
- сохранение пароля для другой сети не удаляет уже сохранённые пароли остальных сетей.
Если сеть раньше уже была успешно подключена и помечена как валидная:
- после потери связи устройство автоматически пытается переподключиться;
@@ -228,13 +292,13 @@
Используется `Preferences` (NVS памяти ESP32):
- `solana_rpc`
- `shine_server`
- `shine_server_login`
## Хранение аккаунта
Используется `Preferences` (NVS памяти ESP32):
- `login`
- `subserver`
- `homeserver`
- `secret_set`
## Детали клавиатуры
@@ -245,6 +309,7 @@
- половина букв находится на левой странице, половина на правой;
- на правой странице кнопки тоже стоят в ровных колонках, без сдвига рядов вправо;
- отдельный режим `symbols` тоже разделён на 2 страницы;
- в режиме `symbols` на правой странице есть отдельная кнопка `SPACE` и отдельная кнопка `!`;
- 3 основных ряда клавиш и нижний служебный ряд увеличены по высоте;
- клавиши дополнительно увеличены по высоте по сравнению с предыдущим промежуточным вариантом;
- четвёртый ряд содержит:
@@ -253,7 +318,7 @@
- `DEL`
- `SAVE`
- `CANCEL`
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`.
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `SHiNE homeserver (v.0.18)`.
## Жесты
@@ -270,7 +335,7 @@
- `WIFI_SCREEN`: свайп вправо -> `SETTINGS_MENU`
- `SERVER_SCREEN`: свайп вправо -> `SETTINGS_MENU`
- `ACCOUNT_SCREEN`: свайп вправо -> `SETTINGS_MENU`
- `ACCOUNT_SUBSERVER_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
- `ACCOUNT_HOMESERVER_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
- `ACCOUNT_SECRET_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
- `TEXT_EDIT_SCREEN`: свайп влево/вправо -> переключение страниц клавиатуры
- переключение страниц клавиатуры срабатывает только если свайп начался в зоне самой клавиатуры, а не по всему экрану редактора
@@ -0,0 +1,681 @@
# SHiNE ESP32 Homeserver UI Spec
## Назначение
Этот документ описывает актуальный UI-прототип homeserver `SHiNE` для платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Актуальный основной Arduino-скетч лежит в `../main-device/shine_homeserver_main/`.
Документ является источником истины для Arduino-скетча:
- если меняется этот документ, должен меняться и скетч;
- если меняется скетч, должен обновляться и этот документ;
- экраны, кнопки, поля, статусы, переходы и тексты не должны расходиться.
## Текущий объём реализации
Текущая реализация является интерактивным прототипом экрана устройства, пригодным для ручной проверки на железе.
Что уже входит в прототип:
- локальный UI на тач-экране;
- хранение настроек и секретов во внутренней памяти `ESP32` через `NVS`;
- экранный UI устройства работает на английском языке, потому что кириллица на этом шрифтовом пути пока не поддерживается стабильно;
- экран пополнения с реальным `solana:` URI и рисованием QR-кода через `LVGL`;
- реальное подключение к `Wi-Fi` по сохранённым `SSID/паролю`;
- реальная проверка доступности `API`, `RPC` и `WS`-адресов;
- реальное чтение баланса кошелька из `Solana RPC`;
- проверка обязательных условий перед регистрацией;
- живая on-chain регистрация серверного `user_pda` в `shine_users` по нажатию кнопки на главном экране;
- живой экран заявок на подключение новых устройств через доверенную homeserver-сессию;
- PIN-блокировка (в текущей временной сборке вход по PIN отключён, устройство открывает `HOME` сразу после старта);
- базовые настройки, статус и главный экран;
- сохранение `PDA` и `tx signature` после успешной регистрации.
- создание и возобновление серверной сессии `SHiNE` через WebSocket с `sessionType = 100` и `clientPlatform = "ESP32"`.
Что пока считается именно прототипом, а не финальной интеграцией:
- пока реализован только сценарий заявок на подключение устройств через доверенную сессию;
- другие типы входящих запросов на подпись и произвольные approval-flow на этом устройстве ещё не подключены.
## Основная идея устройства
Устройство работает как отдельный homeserver:
- хранит секрет на самом устройстве;
- позволяет ввести логин, секрет и имя homeserver;
- показывает адрес кошелька устройства;
- позволяет пополнить баланс перед регистрацией;
- после выполнения условий даёт зарегистрировать устройство как homeserver;
- после авторизации в SHiNE может подтверждать заявки на подключение новых устройств пользователя.
`SD`-карта не нужна для постоянного хранения секрета в этом прототипе.
Основное сохранение идёт во внутреннюю flash-память через `NVS`.
## Данные, которые хранятся на устройстве
Прототип хранит:
- `PIN`;
- `Wi-Fi SSID`;
- `Wi-Fi password`;
- `login`;
- `session/homeserver name`;
- `master secret`;
- `wallet address`;
- `user pda address`;
- `registration signature`;
- `balance`;
- `server login` для первичной привязки;
- `resolved server api url` / `rpc url` / `ws url` после чтения PDA сервера;
- флаги:
`wifiReady`, `serversReady`, `secretReady`, `registered`, `online`.
Для первой регистрации обычного `user PDA` устройство берёт `createdAtMs` из NTP прямо перед отправкой транзакции в Solana. При последующих обновлениях `user PDA` устройство так же берёт актуальный `updatedAtMs` из NTP перед отправкой update-транзакции. Дальше в `user PDA` сохраняется `accessServers`, где по умолчанию лежит `shineupme`.
## Правило серверной сессии SHiNE
При подключении к серверу `SHiNE` устройство должно авторизовываться как homeserver-сеанс:
- `sessionType = 100`
- `clientPlatform = "ESP32"`
- `clientInfo = "ESP32 homeserver:<homeserverName>"`
Это относится и к `CreateAuthSession`, и к `SessionLogin`.
## Правила готовности к регистрации
Кнопка регистрации доступна только если одновременно выполнены условия:
1. настроен и подтверждён `Wi-Fi`;
2. задан и подтверждён `SHiNE server login`;
3. задан логин;
4. сгенерирован или введён секрет;
5. баланс кошелька не меньше `0.20 SOL`;
6. устройство ещё не зарегистрировано.
Если хотя бы одно условие не выполнено, главный экран показывает, чего именно не хватает.
## Экранная модель
В прототипе используются следующие экраны:
1. `LOCK`
2. `HOME`
3. `STATUS`
4. `CONNECTION`
5. `WIFI_EDIT`
6. `SERVERS`
7. `ACCOUNT`
8. `WALLET`
9. `WALLET_QR`
10. `WALLET_SIGN_REQUEST`
11. `REQUESTS`
12. `REQUEST_DETAIL`
13. `SETTINGS`
14. `PIN_EDIT`
15. `TEXT_INPUT`
16. `CONFIRM`
17. `REGISTER_ACCOUNT_CONFIRM`
18. `REGISTER_ACCOUNT_RESULT`
## Общие правила интерфейса
- Верхняя строка всегда показывает краткий статус устройства:
`PIN`, `Wi-Fi`, `сервер`, `регистрация`.
- В правом верхнем углу рядом с батареей:
- сначала процент заряда;
- затем, если устройство реально заряжается, маленькая иконка молнии;
- затем контур батареи;
- затем индикатор `Wi-Fi`.
- Основной язык прототипа: английский.
- Для вывода текста в текущей временной сборке используется стандартный шрифт `Arduino_GFX`.
- Русские строки в UI устройства пока не использовать.
- Кнопки крупные, с тач-ориентированным размером.
- Опасные действия подтверждаются отдельным диалогом.
- После изменения данных конфигурация сразу сохраняется в `NVS`.
## Экран LOCK
Назначение:
- экран блокировки предусмотрен в UI, но в текущей временной сборке после запуска пропускается;
- вход по PIN временно отключён для отладки остальных экранов.
Отображается:
- заголовок `SHiNE Device`;
- статус `Устройство заблокировано`;
- поле ввода PIN в виде маски;
- кнопки цифровой клавиатуры;
- кнопки `Стереть` и `Открыть`.
Поведение:
- если PIN введён верно, открывается `HOME`;
- если PIN неверный, показывается ошибка `Неверный PIN`;
- в текущей временной сборке этот экран не показывается автоматически при старте.
## Экран HOME
Это основной экран устройства.
Показывает:
- крупный статус регистрации;
- имя логина;
- имя homeserver;
- строку `Wi-Fi: <SSID> connected|disconnected`;
- строку `SHiNE: <server address> connected|unavailable`;
- короткий статус баланса.
Особенности верхнего блока:
- зелёный/контурный статусный кружок аккаунта расположен слева от строки логина;
- блок `STATUS` поднят выше относительно предыдущей версии;
- если состояние хорошее, слово `connected` в строках `Wi-Fi` и `SHiNE` показывается зелёным.
В зоне кошелька:
- вместо старых двух кнопок `баланс` и `QR` показывается одна широкая кнопка;
- текст кнопки: `Wallet: <selected wallet name>`;
- доступные имена:
- `ClientKey`
- `RootKey`
- либо сохранённое имя `custom`-кошелька;
- после старта устройства баланс активного выбранного кошелька пытается загрузиться автоматически, если уже есть секрет и `Wi-Fi`;
- нажатие на эту кнопку открывает экран `WALLET`.
Нижние кнопки:
- `Статус`
- `Подключение`
- `Аккаунт`
- `Wallet`
- `Запросы`
- `Настройки`
Дополнительная большая кнопка:
- `REGISTER ACCOUNT`
- либо жёлтая `ADD HOMESERVER`
- либо жёлтая `FIX HOMESERVER PASSWORD`
Если регистрация уже сделана:
- если пользователь создан, но в `PDA` ещё нет сессии текущего homeserver, показывается жёлтая кнопка `ADD HOMESERVER`;
- если в `PDA` есть homeserver с тем же именем, но с другим ключом, показывается жёлтая кнопка `FIX HOMESERVER PASSWORD`;
- если и пользователь, и homeserver-сессия уже корректны, вместо призыва к регистрации показывается статус `Homeserver активен`.
- две нижние кнопки внизу экрана не прилегают вплотную друг к другу, между ними есть небольшой зазор.
## Экран REGISTER_ACCOUNT_CONFIRM
Показывает предварительную проверку перед отправкой транзакции регистрации.
Отображается:
- повторный `login`;
- сообщение о том, что логин свободен или уже занят;
- баланс кошелька с проверкой порога `0.020 SOL`;
- имя homeserver;
- пометка, если используется стандартное значение `homeserver1`;
- отдельное предупреждение, если `Wi-Fi` не подключён.
Кнопки:
- `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`
- `BACK`
Поведение:
- если `Wi-Fi` не подключён, на экране прямо показывается уведомление об этом и кнопка регистрации недоступна;
- если `login` уже занят в `shine_users`, регистрация недоступна;
- если баланс меньше `0.020 SOL`, на экране показывается соответствующая ошибка;
- если все проверки пройдены, кнопка регистрации активна и запускает on-chain регистрацию.
## Экран REGISTER_ACCOUNT_RESULT
Показывает результат отправки регистрации.
Отображается:
- текст успеха или ошибки;
- подробное сообщение;
- при успехе краткий `user_pda`;
- при успехе краткий `tx signature`.
Кнопки:
- `BACK HOME`
- `ACCOUNT`
Поведение:
- после успешной регистрации данные `user_pda` и `tx signature` сохраняются в `NVS`;
- при ошибке на экране показывается причина отказа;
- если ошибку вернул `sendTransaction`, экран старается показать не только общий текст, но и детали `RPC`/preflight/simulate-логов.
## Экран HOMESERVER_PDA_CONFIRM
Показывает, что именно не так с homeserver-секцией уже существующей пользовательской `PDA`.
Отображается:
- причина (`homeserver` отсутствует в `PDA` или ключ не совпадает с локальным секретом);
- `login`;
- имя `homeserver`;
- короткое пояснение, что именно будет сделано.
Кнопки:
- `ADD HOMESERVER` или `FIX HOMESERVER PASSWORD`
- `BACK`
Поведение:
- если `Wi-Fi` не подключён, действие недоступно;
- экран не создаёт нового пользователя, а запускает `update_user_pda`;
- при `ADD HOMESERVER` в блок `sessions` добавляется запись `session_type=100`;
- при `FIX HOMESERVER PASSWORD` обновляется публичный ключ уже существующей записи `homeserver`.
## Экран HOMESERVER_PDA_RESULT
Показывает результат обновления `sessions` в пользовательской `PDA`.
Отображается:
- успех или ошибка;
- короткое сообщение;
- при успехе краткий `tx signature`.
Кнопки:
- `BACK HOME`
- `ACCOUNT`
Поведение:
- при ошибке текст ошибки сохраняется в ту же USB/NVS-диагностику, что и регистрация;
- после успешного обновления выполняется повторная проверка `PDA`, и основной экран должен перейти в состояние `ok`.
## Экран STATUS
Показывает сводку:
- логин;
- homeserver;
- есть ли секрет;
- зарегистрировано ли устройство;
- подключён ли Wi-Fi;
- доступны ли серверы;
- хватает ли баланса;
- находится ли устройство онлайн;
- краткий отпечаток `PDA` или `tx`.
Кнопки:
- `Назад`
- `Обновить статус`
`Обновить статус` в прототипе не делает сеть, а просто перерисовывает текущие вычисленные состояния.
## Экран CONNECTION
Показывает:
- `Wi-Fi`: готов / не готов;
- `Серверы`: готовы / не готовы;
- `Онлайн`: да / нет.
Кнопки:
- `Wi-Fi`
- `Серверы`
- `Подключить`
- `Отключить`
- `Назад`
Поведение:
- `Подключить` переводит устройство в `online=true`, если `Wi-Fi` реально подключён и серверы реально проверены;
- `Отключить` переводит устройство в `online=false`.
## Экран WIFI_EDIT
Поля:
- `SSID`
- `Пароль`
Кнопки:
- `Изменить SSID`
- `Изменить пароль`
- `Проверить`
- `Сбросить`
- `Назад`
Поведение:
- `Проверить` делает реальную попытку подключения к `Wi-Fi`;
- `Сбросить` очищает обе строки и ставит `wifiReady=false`.
## Экран SERVERS
Поля:
- `API URL`
- `RPC URL`
- `WS URL`
Кнопки:
- `Изменить API`
- `Изменить RPC`
- `Изменить WS`
- `Проверить`
- `Тестовые`
- `Назад`
Поведение:
- `Тестовые` подставляет дефолтные тестовые значения;
- `Проверить` делает реальные сетевые проверки:
- `API URL` должен отвечать по `HTTP/HTTPS`;
- `RPC URL` должен отвечать на `Solana JSON-RPC`;
- `WS URL` должен принимать `TCP/TLS`-соединение.
## Экран ACCOUNT
Показывает:
- логин;
- имя homeserver;
- статус секрета;
- короткий отпечаток секрета;
- статус регистрации;
- короткий отпечаток `PDA` или `tx`.
Кнопки:
- `Изменить логин`
- `Секрет`
- `Имя homeserver`
- `Сгенерировать`
- `Очистить`
- `Назад`
Поведение:
- `Сгенерировать` создаёт новый `master secret` и пересчитывает из него `device`-кошелёк;
- `Очистить` удаляет секрет, адрес кошелька, `PDA`, `tx`, регистрацию и онлайн-статус;
- логин приводится к нижнему регистру и trim.
- после успешной регистрации на экране сохраняются и отображаются краткие отпечатки `PDA` и `TX`.
## Экран WALLET
Показывает:
- кнопку `Wallet: <selected wallet name>`;
- строку текущего статуса/баланса активного кошелька;
- сокращённый публичный ключ активного кошелька.
Кнопки:
- `Wallet: <selected wallet name>`
- `SHOW BALANCE`
- `SHOW WALLET QR`
Поведение:
- верхняя кнопка открывает экран выбора кошелька `WALLET_SELECT`;
- `Показать баланс кошелька` читает реальный баланс именно активного выбранного кошелька из `Solana RPC`;
- `Показать QR-код кошелька` открывает экран `WALLET_QR`;
- этот экран не меняет логику Solana-регистрации пользователя: on-chain регистрация и проверки `PDA` по-прежнему завязаны на `client key`.
## Экран WALLET_SELECT
Показывает:
- строку `Current: <selected wallet name>`;
- три кнопки выбора:
- `ClientKey`
- `RootKey`
- `Custom` или `Custom: <имя>`;
- у текущего выбора видна галочка.
Поведение:
- `ClientKey` активирует кошелёк, выведенный из suffix `client.key`;
- `RootKey` активирует кошелёк, выведенный из suffix `root.key`;
- `Custom` использует derivation:
`sha256(base64(secret32) + "|wallet." + customName)`;
- если имя `custom`-кошелька ещё не задано, нажатие `Custom` открывает экран текстового ввода;
- если `custom` уже выбран, повторное нажатие на него открывает экран редактирования имени;
- после выбора кошелька устройство возвращается на экран `WALLET`.
## Экран WALLET_QR
Экран показывает:
- крупный реальный `QR` для строки `solana:<wallet_address>`;
- снизу крупный текст самого адреса кошелька.
Поведение:
- отдельная текстовая подсказка возврата не показывается;
- возврат на главный экран выполняется обычным тапом по экрану.
Показывает:
- QR-код для строки вида:
`solana:<wallet>`;
- мелкую подпись с полным адресом кошелька под QR.
Поведение:
- QR должен быть сканируемым, а не декоративным;
- адрес кошелька берётся из текущего активного выбранного кошелька;
- нажатие в любую точку экрана возвращает пользователя на `WALLET`.
## Экран WALLET_SIGN_REQUEST
Экран показывается, когда браузерное wallet-расширение присылает на ESP32 запрос `sign_transaction`.
Показывает:
- заголовок `SIGN REQUEST`;
- вопрос о подписи транзакции текущим активным кошельком;
- сокращённый публичный ключ кошелька;
- комментарий, присланный полем `comment`;
- две кнопки:
- `REJECT`
- `APPROVE`
Поведение:
- если пользователь нажимает `APPROVE`, ESP32 подписывает транзакцию текущим активным кошельком и отправляет ответ в расширение;
- если пользователь нажимает `REJECT`, ESP32 отправляет отказ `rejected_by_user`;
- пока запрос активен, свайпом этот экран не закрывается;
- после ответа расширению устройство возвращается на предыдущий экран.
## Экран REQUESTS
Экран доступен только если homeserver уже авторизован в SHiNE.
Показывает:
- слева сверху кнопку `REFRESH`;
- заголовок `REQUESTS:` немного правее стандартного левого положения;
- справа сверху только большую цифру числа активных заявок;
- ниже прокручиваемый список активных pairing-заявок;
- на экране одновременно видны примерно две плитки.
Каждая плитка показывает:
- код подключения из `10` цифр в виде `5` пар: `XX XX XX XX XX`;
- строку `Session: <platform/name>`;
- строку `Kind: Client session` или `Kind: Wallet session`.
Поведение:
- список берётся из живой операции `ListTrustedDeviceLoginRequests`;
- если заявок нет, экран показывает `No active requests`;
- отдельная строка вида `Active requests: N` на этом экране не показывается;
- вертикальный скролл позволяет просматривать все активные заявки;
- нажатие по плитке открывает `REQUEST_DETAIL`;
- свайп вправо возвращает в `SETTINGS`.
## Экран REQUEST_DETAIL
Показывает детали выбранной pairing-заявки:
- вопрос `Connect session ...?`;
- код подключения `XX XX XX XX XX`;
- строку `Session: <platform/name>`;
- строку `Kind: Client session` или `Kind: Wallet session`;
- пояснение:
- для client session: `Only client key will be transferred. No additional keys will be sent.`
- для wallet session: `No keys will be transferred.`
Кнопки:
- `YES`
- `NO`
Поведение:
- `YES` подтверждает заявку:
- для client session устройство передаёт только `client key`;
- для wallet session устройство выпускает отдельную `wallet-session` без передачи ключей;
- `NO` отклоняет заявку;
- после любого решения устройство возвращается в список `REQUESTS` и обновляет его;
- свайп вправо возвращает в `REQUESTS`.
## Экран SETTINGS
Показывает вертикальное меню крупных пунктов.
Если homeserver ещё не авторизован в SHiNE:
- `1. Wi-Fi`
- `2. Server`
- `3. Account`
Если homeserver уже авторизован:
- `1. Device requests`
- `2. Wi-Fi`
- `3. Server`
- `4. Account`
Поведение:
- одновременно видны только две карточки;
- список листается свайпом вверх/вниз;
- свайп вправо возвращает на `HOME`;
- пункт `Device requests` должен быть первым и появляется только для авторизованного homeserver.
## Экран PIN_EDIT
Используется для ввода нового PIN.
Правила:
- допустимы только цифры;
- длина PIN: 4..8 символов;
- после сохранения новый PIN немедленно пишется в `NVS`.
## Экран TEXT_INPUT
Это общий экран редактирования текстовых полей.
Используется для:
- `SSID`
- `Пароль Wi-Fi`
- `Логин`
- `Имя homeserver`
- `API URL`
- `RPC URL`
- `WS URL`
Состав экрана:
- заголовок поля;
- текущее значение;
- программная клавиатура;
- кнопки `Стереть`, `OK`, `Отмена`.
## Экран CONFIRM
Это модальный экран подтверждения.
Используется для:
- регистрации;
- очистки секрета;
- полного сброса.
Кнопки:
- `Подтвердить`
- `Отмена`
## Сценарий первой настройки
Ожидаемый путь пользователя:
1. открыть устройство сразу после старта; экран PIN временно не требуется;
2. открыть `Подключение -> Wi-Fi`;
3. ввести `SSID` и пароль, нажать `Проверить`;
4. открыть `Подключение -> Серверы`;
5. проверить или задать `SHiNE server login` (по умолчанию `shineupme`);
6. открыть `Аккаунт`;
7. ввести логин;
8. задать имя homeserver;
9. сгенерировать секрет;
10. открыть `Кошелёк`;
11. при необходимости пополнить баланс;
12. вернуться на `HOME`;
13. нажать `REGISTER ACCOUNT`;
14. на экране проверки ещё раз увидеть `login`, статус свободного `PDA`, баланс, `homeserver1`, серверный login и при необходимости сообщение о неподключённом `Wi-Fi`;
15. нажать `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`;
16. после завершения увидеть либо экран успеха с `user_pda` и `tx signature`, либо подробную ошибку;
17. после успешной регистрации увидеть статус `Homeserver активен`.
Примечание:
- устройство реально отправляет `create_user_pda` в `shine_users`, а после подтверждения сохраняет `PDA` и `tx signature`;
- при первой регистрации для обычного `user PDA` не заполняется `serverAddress`, а `accessServers` получает `shineupme` или другой выбранный `SHiNE server login`.
## Сценарий входящего запроса
1. открыть `Запросы`;
2. выбрать один из запросов;
3. прочитать детали;
4. нажать `Разрешить` или `Отклонить`;
5. убедиться, что статус запроса изменился.
## Временный режим текста
В текущей диагностической версии:
- строковые литералы экранного UI должны оставаться английскими ASCII-совместимыми;
- возвращение кириллицы допустимо только после отдельной доработки шрифтов и реальной проверки на устройстве;
- рендер выполняется стандартным шрифтом `Arduino_GFX`;
- это обходной режим, пока `U8g2`-шрифты на устройстве не начнут рисоваться стабильно.
## Критерии ручной проверки
Минимально нужно проверить:
1. устройство загружается и сразу открывает `HOME`; экран блокировки временно отключён;
2. весь экранный текст отображается читаемо на английском без замены символов;
3. ввод по экранной клавиатуре работает;
4. после перезагрузки сохранённые поля остаются в памяти;
5. секрет и адрес кошелька сохраняются на устройстве;
6. экран `QR и URI` рисует читаемый QR-код;
7. регистрация блокируется, пока условия не выполнены;
8. после выполнения условий регистрация становится доступной;
9. список запросов и экран подтверждения работают;
10. полный сброс действительно очищает сохранённое состояние.
@@ -1,477 +0,0 @@
# SHiNE ESP32 Subserver UI Spec
## Назначение
Этот документ описывает актуальный UI-прототип сабсервера `SHiNE` для платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Документ является источником истины для Arduino-скетча:
- если меняется этот документ, должен меняться и скетч;
- если меняется скетч, должен обновляться и этот документ;
- экраны, кнопки, поля, статусы, переходы и тексты не должны расходиться.
## Текущий объём реализации
Текущая реализация является интерактивным прототипом экрана устройства, пригодным для ручной проверки на железе.
Что уже входит в прототип:
- локальный UI на тач-экране;
- хранение настроек и секретов во внутренней памяти `ESP32` через `NVS`;
- русский текст в логике интерфейса; в текущей временной сборке отображение на экране идёт через ASCII-транслитерацию, потому что `U8g2`-шрифты на устройстве временно не рисуются;
- экран пополнения с реальным `solana:` URI и рисованием QR-кода;
- реальное подключение к `Wi-Fi` по сохранённым `SSID/паролю`;
- реальная проверка доступности `API`, `RPC` и `WS`-адресов;
- реальное чтение баланса кошелька из `Solana RPC`;
- проверка обязательных условий перед регистрацией;
- живая on-chain регистрация серверного `user_pda` в `shine_users` через `device key` устройства;
- прототип входящих запросов с подтверждением и отклонением;
- PIN-блокировка (в текущей временной сборке вход по PIN отключён, устройство открывает `HOME` сразу после старта);
- базовые настройки, статус и главный экран;
- сохранение `PDA` и `tx signature` после успешной регистрации.
Что пока считается именно прототипом, а не финальной интеграцией:
- приём реальных входящих запросов на вход/подпись пока не подключён к живой сети;
- входящие запросы пока демонстрационные, чтобы можно было проверить UX и логику подтверждения.
## Основная идея устройства
Устройство работает как отдельный сабсервер:
- хранит секрет на самом устройстве;
- позволяет ввести логин, секрет и имя сабсервера;
- показывает адрес кошелька устройства;
- позволяет пополнить баланс перед регистрацией;
- после выполнения условий даёт зарегистрировать устройство как сабсервер;
- после регистрации может принимать входящие запросы на вход и на подпись.
`SD`-карта не нужна для постоянного хранения секрета в этом прототипе.
Основное сохранение идёт во внутреннюю flash-память через `NVS`.
## Данные, которые хранятся на устройстве
Прототип хранит:
- `PIN`;
- `Wi-Fi SSID`;
- `Wi-Fi password`;
- `login`;
- `session/subserver name`;
- `master secret`;
- `wallet address`;
- `user pda address`;
- `registration signature`;
- `balance`;
- `server api url`;
- `server rpc url`;
- `server ws url`;
- флаги:
`wifiReady`, `serversReady`, `secretReady`, `registered`, `online`.
## Правила готовности к регистрации
Кнопка регистрации доступна только если одновременно выполнены условия:
1. настроен и подтверждён `Wi-Fi`;
2. заполнены и подтверждены серверные адреса;
3. задан логин;
4. сгенерирован или введён секрет;
5. баланс кошелька не меньше `0.20 SOL`;
6. устройство ещё не зарегистрировано.
Если хотя бы одно условие не выполнено, главный экран показывает, чего именно не хватает.
## Экранная модель
В прототипе используются следующие экраны:
1. `LOCK`
2. `HOME`
3. `STATUS`
4. `CONNECTION`
5. `WIFI_EDIT`
6. `SERVERS`
7. `ACCOUNT`
8. `WALLET`
9. `WALLET_QR`
10. `REQUESTS`
11. `REQUEST_DETAIL`
12. `SETTINGS`
13. `PIN_EDIT`
14. `TEXT_INPUT`
15. `CONFIRM`
## Общие правила интерфейса
- Верхняя строка всегда показывает краткий статус устройства:
`PIN`, `Wi-Fi`, `сервер`, `регистрация`.
- Основной язык прототипа: русский.
- Для вывода текста в текущей временной сборке используется стандартный шрифт `Arduino_GFX`.
- Русские строки на экране временно показываются в ASCII-транслитерации.
- Кнопки крупные, с тач-ориентированным размером.
- Опасные действия подтверждаются отдельным диалогом.
- После изменения данных конфигурация сразу сохраняется в `NVS`.
## Экран LOCK
Назначение:
- экран блокировки предусмотрен в UI, но в текущей временной сборке после запуска пропускается;
- вход по PIN временно отключён для отладки остальных экранов.
Отображается:
- заголовок `SHiNE Device`;
- статус `Устройство заблокировано`;
- поле ввода PIN в виде маски;
- кнопки цифровой клавиатуры;
- кнопки `Стереть` и `Открыть`.
Поведение:
- если PIN введён верно, открывается `HOME`;
- если PIN неверный, показывается ошибка `Неверный PIN`;
- в текущей временной сборке этот экран не показывается автоматически при старте.
## Экран HOME
Это основной экран устройства.
Показывает:
- крупный статус регистрации;
- имя логина;
- имя сабсервера;
- короткий статус Wi-Fi;
- короткий статус сервера;
- короткий статус баланса.
Нижние кнопки:
- `Статус`
- `Подключение`
- `Аккаунт`
- `Кошелёк`
- `Запросы`
- `Настройки`
Дополнительная большая кнопка:
- `Зарегистрировать`
Если регистрация уже сделана:
- вместо призыва к регистрации показывается статус `Сабсервер активен`.
## Экран STATUS
Показывает сводку:
- логин;
- сабсервер;
- есть ли секрет;
- зарегистрировано ли устройство;
- подключён ли Wi-Fi;
- доступны ли серверы;
- хватает ли баланса;
- находится ли устройство онлайн;
- краткий отпечаток `PDA` или `tx`.
Кнопки:
- `Назад`
- `Обновить статус`
`Обновить статус` в прототипе не делает сеть, а просто перерисовывает текущие вычисленные состояния.
## Экран CONNECTION
Показывает:
- `Wi-Fi`: готов / не готов;
- `Серверы`: готовы / не готовы;
- `Онлайн`: да / нет.
Кнопки:
- `Wi-Fi`
- `Серверы`
- `Подключить`
- `Отключить`
- `Назад`
Поведение:
- `Подключить` переводит устройство в `online=true`, если `Wi-Fi` реально подключён и серверы реально проверены;
- `Отключить` переводит устройство в `online=false`.
## Экран WIFI_EDIT
Поля:
- `SSID`
- `Пароль`
Кнопки:
- `Изменить SSID`
- `Изменить пароль`
- `Проверить`
- `Сбросить`
- `Назад`
Поведение:
- `Проверить` делает реальную попытку подключения к `Wi-Fi`;
- `Сбросить` очищает обе строки и ставит `wifiReady=false`.
## Экран SERVERS
Поля:
- `API URL`
- `RPC URL`
- `WS URL`
Кнопки:
- `Изменить API`
- `Изменить RPC`
- `Изменить WS`
- `Проверить`
- `Тестовые`
- `Назад`
Поведение:
- `Тестовые` подставляет дефолтные тестовые значения;
- `Проверить` делает реальные сетевые проверки:
- `API URL` должен отвечать по `HTTP/HTTPS`;
- `RPC URL` должен отвечать на `Solana JSON-RPC`;
- `WS URL` должен принимать `TCP/TLS`-соединение.
## Экран ACCOUNT
Показывает:
- логин;
- имя сабсервера;
- статус секрета;
- короткий отпечаток секрета;
- статус регистрации;
- короткий отпечаток `PDA` или `tx`.
Кнопки:
- `Изменить логин`
- `Секрет`
- `Имя сабсервера`
- `Сгенерировать`
- `Очистить`
- `Назад`
Поведение:
- `Сгенерировать` создаёт новый `master secret` и пересчитывает из него `device`-кошелёк;
- `Очистить` удаляет секрет, адрес кошелька, `PDA`, `tx`, регистрацию и онлайн-статус;
- логин приводится к нижнему регистру и trim.
## Экран WALLET
Показывает:
- адрес кошелька устройства;
- баланс в `SOL`;
- минимально рекомендуемую сумму для регистрации;
- статус `Хватает / Не хватает`.
Кнопки:
- `QR и URI`
- `+0.10 SOL`
- `+0.25 SOL`
- `-0.10 SOL`
- `Проверить`
- `Назад`
Поведение:
- кнопки пополнения/уменьшения нужны для теста сценариев;
- `Проверить` читает реальный баланс из `Solana RPC`;
- адрес кошелька должен совпадать с `device key`, вычисленным из сохранённого `master secret`;
- отрицательный баланс не допускается.
## Экран WALLET_QR
Показывает:
- QR-код для строки вида:
`solana:<wallet>?amount=0.20&label=SHiNE%20Register`;
- адрес кошелька;
- сумму;
- текст URI.
Кнопки:
- `Назад`
QR должен быть сканируемым, а не декоративным.
## Экран REQUESTS
Показывает список демонстрационных запросов:
- `Вход в сессию`
- `Подпись сообщения`
Для каждого запроса:
- тип;
- источник;
- короткий статус.
Кнопки:
- `Открыть запрос 1`
- `Открыть запрос 2`
- `Назад`
## Экран REQUEST_DETAIL
Показывает детали выбранного запроса:
- тип запроса;
- кто запросил;
- время;
- описание;
- отпечаток/идентификатор.
Кнопки:
- `Разрешить`
- `Отклонить`
- `Назад`
Поведение:
- после разрешения или отклонения запрос помечается обработанным;
- экран списка отражает новый статус.
## Экран SETTINGS
Показывает:
- текущий PIN;
- базовые флаги безопасности;
- технические действия прототипа.
Кнопки:
- `Сменить PIN`
- `Сбросить онлайн`
- `Полный сброс`
- `Назад`
`Полный сброс` очищает весь локальный конфиг и возвращает устройство к стартовому состоянию.
## Экран PIN_EDIT
Используется для ввода нового PIN.
Правила:
- допустимы только цифры;
- длина PIN: 4..8 символов;
- после сохранения новый PIN немедленно пишется в `NVS`.
## Экран TEXT_INPUT
Это общий экран редактирования текстовых полей.
Используется для:
- `SSID`
- `Пароль Wi-Fi`
- `Логин`
- `Имя сабсервера`
- `API URL`
- `RPC URL`
- `WS URL`
Состав экрана:
- заголовок поля;
- текущее значение;
- программная клавиатура;
- кнопки `Стереть`, `OK`, `Отмена`.
## Экран CONFIRM
Это модальный экран подтверждения.
Используется для:
- регистрации;
- очистки секрета;
- полного сброса.
Кнопки:
- `Подтвердить`
- `Отмена`
## Сценарий первой настройки
Ожидаемый путь пользователя:
1. открыть устройство сразу после старта; экран PIN временно не требуется;
2. открыть `Подключение -> Wi-Fi`;
3. ввести `SSID` и пароль, нажать `Проверить`;
4. открыть `Подключение -> Серверы`;
5. проверить или задать серверные адреса;
6. открыть `Аккаунт`;
7. ввести логин;
8. задать имя сабсервера;
9. сгенерировать секрет;
10. открыть `Кошелёк`;
11. при необходимости пополнить баланс;
12. вернуться на `HOME`;
13. нажать `Зарегистрировать`;
14. после подтверждения увидеть статус `Сабсервер активен`.
Примечание:
- устройство реально отправляет `create_user_pda` в `shine_users`, а после подтверждения сохраняет `PDA` и `tx signature`.
## Сценарий входящего запроса
1. открыть `Запросы`;
2. выбрать один из запросов;
3. прочитать детали;
4. нажать `Разрешить` или `Отклонить`;
5. убедиться, что статус запроса изменился.
## Временный режим текста
В текущей диагностической версии:
- строковые литералы в коде остаются русскими и в `UTF-8`;
- перед выводом на экран они временно транслитерируются в ASCII;
- рендер выполняется стандартным шрифтом `Arduino_GFX`;
- это обходной режим, пока `U8g2`-шрифты на устройстве не начнут рисоваться стабильно.
## Критерии ручной проверки
Минимально нужно проверить:
1. устройство загружается и сразу открывает `HOME`; экран блокировки временно отключён;
2. текст отображается читаемо хотя бы в ASCII-транслитерации;
3. ввод по экранной клавиатуре работает;
4. после перезагрузки сохранённые поля остаются в памяти;
5. секрет и адрес кошелька сохраняются на устройстве;
6. экран `QR и URI` рисует читаемый QR-код;
7. регистрация блокируется, пока условия не выполнены;
8. после выполнения условий регистрация становится доступной;
9. список запросов и экран подтверждения работают;
10. полный сброс действительно очищает сохранённое состояние.
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Some files were not shown because too many files have changed in this diff Show More