SHA256
Смержить production-структуру документации
# Conflicts: # VERSION.properties # shine-UI/js/pages/registration-payment-view.js # shine-UI/styles/components.css
This commit is contained in:
@@ -38,43 +38,43 @@
|
|||||||
- В git добавлять исходники, lock-файлы, настройки проекта и документацию Solana-модуля, но не добавлять локальные ключи, `.git`, `.idea`, `.gradle`, `target`, `node_modules`, `test-ledger`, логи, временные run-отчёты и `.env`-конфиги.
|
- В git добавлять исходники, lock-файлы, настройки проекта и документацию Solana-модуля, но не добавлять локальные ключи, `.git`, `.idea`, `.gradle`, `target`, `node_modules`, `test-ledger`, логи, временные run-отчёты и `.env`-конфиги.
|
||||||
- Для Solana deploy/push использовать правила из локального `shine-solana/shine/AGENTS.md`; не смешивать deploy Solana-модуля с `deployServer`/`deployUI` основного проекта.
|
- Для Solana deploy/push использовать правила из локального `shine-solana/shine/AGENTS.md`; не смешивать deploy Solana-модуля с `deployServer`/`deployUI` основного проекта.
|
||||||
- Для регистрации пользователей в Solana (программа `shine_users`) единая актуальная инструкция по деплою/инициализации, адресам программ, и куда их прописывать в UI/сервере находится в:
|
- Для регистрации пользователей в Solana (программа `shine_users`) единая актуальная инструкция по деплою/инициализации, адресам программ, и куда их прописывать в UI/сервере находится в:
|
||||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
- `docs/Инициализация_Solana_регистрации/README.md`
|
||||||
- Этот файл считать основной справкой (single source of truth) по деплою и первичной инициализации Solana-регистрации в текущем проекте.
|
- Этот файл считать основной справкой (single source of truth) по деплою и первичной инициализации Solana-регистрации в текущем проекте.
|
||||||
- Актуальная архитектурная справка по устройству Solana-программ, PDA-счетам, ролям DAO и движению средств находится в:
|
- Актуальная архитектурная справка по устройству Solana-программ, PDA-счетам, ролям DAO и движению средств находится в:
|
||||||
- `Dev_Docs/Solana_Architecture/README.md`
|
- `docs/Solana_Architecture/README.md`
|
||||||
- Документ формата пользовательской PDA-записи `shine_users` находится в:
|
- Документ формата пользовательской PDA-записи `shine_users` находится в:
|
||||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
||||||
|
|
||||||
## Документация блокчейна
|
## Документация блокчейна
|
||||||
- Актуальная документация по форматам блокчейна находится в `Dev_Docs/Blockchain/README.md`.
|
- Актуальная документация по форматам блокчейна находится в `docs/Blockchain/README.md`.
|
||||||
- Это точка входа (оглавление), рядом расположены детальные файлы по форматам, типам каналов и командным сообщениям.
|
- Это точка входа (оглавление), рядом расположены детальные файлы по форматам, типам каналов и командным сообщениям.
|
||||||
- При любом изменении кода, связанного с блокчейном (формат блока, типы каналов, правила чтения/записи, команды), обязательно обновлять соответствующие документы в `Dev_Docs/Blockchain/`.
|
- При любом изменении кода, связанного с блокчейном (формат блока, типы каналов, правила чтения/записи, команды), обязательно обновлять соответствующие документы в `docs/Blockchain/`.
|
||||||
- Дополнительно обязательно вести `Dev_Docs/Blockchain/CHANGELOG.md`: дописывать изменения построчно с указанием даты/времени и хэша коммита, после которого внесено изменение.
|
- Дополнительно обязательно вести `docs/Blockchain/CHANGELOG.md`: дописывать изменения построчно с указанием даты/времени и хэша коммита, после которого внесено изменение.
|
||||||
- Перед любым изменением формата блокчейна обязательно заранее предупреждать пользователя, что формат будет изменён.
|
- Перед любым изменением формата блокчейна обязательно заранее предупреждать пользователя, что формат будет изменён.
|
||||||
- Изменять формат блокчейна можно только после явного подтверждения пользователя (без подтверждения формат не менять).
|
- Изменять формат блокчейна можно только после явного подтверждения пользователя (без подтверждения формат не менять).
|
||||||
- Добавление любых данных в блокчейн выполнять только через операцию `AddBlock`.
|
- Добавление любых данных в блокчейн выполнять только через операцию `AddBlock`.
|
||||||
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
|
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
|
||||||
|
|
||||||
## Документация личных сообщений (DM)
|
## Документация личных сообщений (DM)
|
||||||
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`.
|
- Актуальная документация по логике личных сообщений находится в `docs/Personal_Messages/Протокол_DM_v1.md`.
|
||||||
- Точный байтовый формат DM находится в `Dev_Docs/Personal_Messages/Формат_DM_v1.md`.
|
- Точный байтовый формат DM находится в `docs/Personal_Messages/Формат_DM_v1.md`.
|
||||||
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять оба документа:
|
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять оба документа:
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
- `docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`
|
- `docs/Personal_Messages/Формат_DM_v1.md`
|
||||||
- Логика личных сообщений в коде должна всегда соответствовать этим документам.
|
- Логика личных сообщений в коде должна всегда соответствовать этим документам.
|
||||||
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
|
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
|
||||||
|
|
||||||
## Документация API сервера
|
## Документация API сервера
|
||||||
- Актуальная документация по публичному JSON/WebSocket API сервера находится в `Dev_Docs/API/`.
|
- Актуальная документация по публичному JSON/WebSocket API сервера находится в `docs/API/`.
|
||||||
- При любом изменении серверного API/эндпоинтов/операций `op` обязательно обновлять соответствующие документы в `Dev_Docs/API/`.
|
- При любом изменении серверного API/эндпоинтов/операций `op` обязательно обновлять соответствующие документы в `docs/API/`.
|
||||||
- Перед изменением самого серверного API обязательно явно предупредить пользователя, какие операции, поля запросов/ответов или коды ошибок будут изменены, и запросить отдельное подтверждение.
|
- Перед изменением самого серверного API обязательно явно предупредить пользователя, какие операции, поля запросов/ответов или коды ошибок будут изменены, и запросить отдельное подтверждение.
|
||||||
- Без явного подтверждения пользователя формат серверного API не менять; допускается только приведение документации в соответствие уже существующему коду.
|
- Без явного подтверждения пользователя формат серверного API не менять; допускается только приведение документации в соответствие уже существующему коду.
|
||||||
- Если добавляется новая операция `op`, нужно обновить общий список операций в `Dev_Docs/API/09_Operations_Index.md` или создать его, если файла ещё нет.
|
- Если добавляется новая операция `op`, нужно обновить общий список операций в `docs/API/09_Operations_Index.md` или создать его, если файла ещё нет.
|
||||||
|
|
||||||
## Документация Figma
|
## Документация Figma
|
||||||
- Актуальная документация по переносу экранов SHiNE в Figma и обратному переносу из Figma в код находится в `Dev_Docs/Figma/`.
|
- Актуальная документация по переносу экранов SHiNE в Figma и обратному переносу из Figma в код находится в `docs/Figma/`.
|
||||||
- Точка входа: `Dev_Docs/Figma/README.md`.
|
- Точка входа: `docs/Figma/README.md`.
|
||||||
- Подробный рабочий регламент: `Dev_Docs/Figma/TRANSFER_UI_SCREENS.md`.
|
- Подробный рабочий регламент: `docs/Figma/TRANSFER_UI_SCREENS.md`.
|
||||||
- Для экранов регистрации, входа и других чувствительных UI-flow по умолчанию переносить экраны в Figma по одному, а не пачкой, если пользователь отдельно не подтвердил иной способ.
|
- Для экранов регистрации, входа и других чувствительных UI-flow по умолчанию переносить экраны в Figma по одному, а не пачкой, если пользователь отдельно не подтвердил иной способ.
|
||||||
|
|
||||||
## Версионирование
|
## Версионирование
|
||||||
@@ -124,8 +124,8 @@
|
|||||||
- В этих записях искать поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
|
- В этих записях искать поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
|
||||||
|
|
||||||
## Недопроверенные фичи (обязательно)
|
## Недопроверенные фичи (обязательно)
|
||||||
- Папка для учёта недопроверенных фич: `Dev_Docs/Pending_Features/`.
|
- Папка для учёта недопроверенных фич: `docs/Pending_Features/`.
|
||||||
- По каждой новой доработке, которая требует ручной проверки, добавлять отдельный markdown-файл в `Dev_Docs/Pending_Features/`.
|
- По каждой новой доработке, которая требует ручной проверки, добавлять отдельный markdown-файл в `docs/Pending_Features/`.
|
||||||
- Рекомендуемый формат имени файла: `YYYY-MM-DD_HHMM_<short-feature-name>.md`.
|
- Рекомендуемый формат имени файла: `YYYY-MM-DD_HHMM_<short-feature-name>.md`.
|
||||||
- Имена новых файлов и краткие описания фич по возможности писать на русском языке.
|
- Имена новых файлов и краткие описания фич по возможности писать на русском языке.
|
||||||
- Внутри файла обязательно указывать:
|
- Внутри файла обязательно указывать:
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
- ожидаемый результат;
|
- ожидаемый результат;
|
||||||
- статус (например: `pending`, `in_progress`, `done`).
|
- статус (например: `pending`, `in_progress`, `done`).
|
||||||
- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
|
- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
|
||||||
- В `Dev_Docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
|
- В `docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
|
||||||
|
|
||||||
## Будущие фичи / TODO
|
## Будущие фичи / TODO
|
||||||
- Папка для задач, сознательно отложенных на будущее: `TODO/`.
|
- Папка для задач, сознательно отложенных на будущее: `TODO/`.
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/` и тематическим подпапкам.
|
- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/` и тематическим подпапкам.
|
||||||
- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `TODO/README.md`, затем при необходимости конкретные файлы из подпапок.
|
- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `TODO/README.md`, затем при необходимости конкретные файлы из подпапок.
|
||||||
- Файлы из этой папки не считать активными задачами и не начинать реализацию без явной просьбы пользователя.
|
- Файлы из этой папки не считать активными задачами и не начинать реализацию без явной просьбы пользователя.
|
||||||
- Старую папку `Dev_Docs/Future_Features/` считать выведенной из использования и больше не использовать для новых записей.
|
- Старую папку `docs/Future_Features/` считать выведенной из использования и больше не использовать для новых записей.
|
||||||
- Если часть кода временно отключена или закомментирована, либо удалена как временная заглушка, в TODO-файле подробно описывать:
|
- Если часть кода временно отключена или закомментирована, либо удалена как временная заглушка, в TODO-файле подробно описывать:
|
||||||
- какие файлы и участки отключены;
|
- какие файлы и участки отключены;
|
||||||
- что осталось в коде как заготовка;
|
- что осталось в коде как заготовка;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
- Solana/Anchor-модуль `shine-solana/shine/`;
|
- Solana/Anchor-модуль `shine-solana/shine/`;
|
||||||
- Telegram-агент-кодер `SHiNE-agent-bot-coder/`;
|
- Telegram-агент-кодер `SHiNE-agent-bot-coder/`;
|
||||||
- TURN-сервер;
|
- TURN-сервер;
|
||||||
- документация `Dev_Docs/`;
|
- документация `docs/`;
|
||||||
- отдельные рабочие папки игроков `Players/`.
|
- отдельные рабочие папки игроков `Players/`.
|
||||||
|
|
||||||
Без явных инструкций агент может путать эти зоны: например, смешать деплой Solana с деплоем сервера, изменить код от имени игрока, не обновить документацию API/DM/блокчейна или неправильно трактовать файл инструкций.
|
Без явных инструкций агент может путать эти зоны: например, смешать деплой Solana с деплоем сервера, изменить код от имени игрока, не обновить документацию API/DM/блокчейна или неправильно трактовать файл инструкций.
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
- `far/` - дальнее будущее без понятного срока.
|
- `far/` - дальнее будущее без понятного срока.
|
||||||
- Если пользователь спрашивает, какие есть планы или что можно продолжить, нужно смотреть эти три папки и отвечать кратким списком по горизонтам.
|
- Если пользователь спрашивает, какие есть планы или что можно продолжить, нужно смотреть эти три папки и отвечать кратким списком по горизонтам.
|
||||||
- Файлы из `TODO/` не начинать реализовывать без явной команды пользователя.
|
- Файлы из `TODO/` не начинать реализовывать без явной команды пользователя.
|
||||||
- После реализации фичи, требующей ручной проверки, нужно добавить отдельный файл в `Dev_Docs/Pending_Features/`.
|
- После реализации фичи, требующей ручной проверки, нужно добавить отдельный файл в `docs/Pending_Features/`.
|
||||||
|
|
||||||
## Центр задач и предложений
|
## Центр задач и предложений
|
||||||
- Сервис хранит простые задачи и предложения в `data/task_center/items.json`.
|
- Сервис хранит простые задачи и предложения в `data/task_center/items.json`.
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ shine-UI/server-ui.html
|
|||||||
- `shine_users`: `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
|
- `shine_users`: `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
|
||||||
- `shine_payments`: `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
|
- `shine_payments`: `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
|
||||||
|
|
||||||
Подробнее: `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
Подробнее: `docs/Инициализация_Solana_регистрации/README.md`
|
||||||
|
|
||||||
## Синхронизация с партнёрскими серверами
|
## Синхронизация с партнёрскими серверами
|
||||||
|
|
||||||
Сервер должен синхронизировать блоки блокчейна и DM с серверами-партнёрами из `sync_servers`.
|
Сервер должен синхронизировать блоки блокчейна и DM с серверами-партнёрами из `sync_servers`.
|
||||||
Детали: `Dev_Docs/Blockchain/sync-between-servers.md`
|
Детали: `docs/Blockchain/sync-between-servers.md`
|
||||||
|
|
||||||
## Деплой
|
## Деплой
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -268,7 +268,7 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Репосты временно отключены до будущей реализации.
|
// Репосты временно отключены до будущей реализации.
|
||||||
// Точка возврата: Dev_Docs/Future_Features/2026-05-24_1140_репосты_в_каналах_и_тредах.md
|
// Точка возврата: docs/Future_Features/2026-05-24_1140_репосты_в_каналах_и_тредах.md
|
||||||
if ((block.type & 0xFFFF) == 1
|
if ((block.type & 0xFFFF) == 1
|
||||||
&& (block.subType & 0xFFFF) == (MsgSubType.TEXT_REPOST & 0xFFFF)) {
|
&& (block.subType & 0xFFFF) == (MsgSubType.TEXT_REPOST & 0xFFFF)) {
|
||||||
log.warn("AddBlock: repost_disabled (login={}, blockchainName={}, blockNumber={})",
|
log.warn("AddBlock: repost_disabled (login={}, blockchainName={}, blockNumber={})",
|
||||||
|
|||||||
@@ -1,220 +0,0 @@
|
|||||||
# Задача 01: Доработка вкладки «Каналы» (UI + API)
|
|
||||||
|
|
||||||
## Кратко и по делу
|
|
||||||
Нужно довести вторую вкладку «Каналы» до полностью рабочего состояния на реальных данных сервера.
|
|
||||||
|
|
||||||
Что должно работать:
|
|
||||||
- список каналов;
|
|
||||||
- вход в канал и чтение сообщений;
|
|
||||||
- вход в тред сообщения (история/ветка);
|
|
||||||
- ответ на сообщение;
|
|
||||||
- лайк/снятие лайка;
|
|
||||||
- подписка на пользователя;
|
|
||||||
- подписка на канал;
|
|
||||||
- видимое имя канала в формате `имя_пользователя/имя_канала`.
|
|
||||||
|
|
||||||
Запись любых новых сущностей делается через `AddBlock` с подписью на клиенте.
|
|
||||||
Чтение делается через 3 API:
|
|
||||||
- `ListSubscriptionsFeed`
|
|
||||||
- `GetChannelMessages`
|
|
||||||
- `GetMessageThread`
|
|
||||||
|
|
||||||
Техническая особенность (оставляем как есть):
|
|
||||||
- на экране каналов индикатор непрочитанного = общее число сообщений канала.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Подробное ТЗ
|
|
||||||
|
|
||||||
### 1. Цель
|
|
||||||
Сделать рабочий каналовый сценарий «от списка до треда», где чтение строится на RPC API, а запись действий пользователя — только через `AddBlock`.
|
|
||||||
|
|
||||||
### 2. Что уже есть в проекте
|
|
||||||
|
|
||||||
#### 2.1 UI (частично)
|
|
||||||
- Есть страницы:
|
|
||||||
- `channels-list`
|
|
||||||
- `channel-view`
|
|
||||||
- `add-channel-view`
|
|
||||||
- Есть запросы чтения в клиенте:
|
|
||||||
- `authService.listSubscriptionsFeed(...)`
|
|
||||||
- `authService.getChannelMessages(...)`
|
|
||||||
- `authService.getMessageThread(...)`
|
|
||||||
- Есть fallback на mock-данные при ошибках сервера.
|
|
||||||
|
|
||||||
#### 2.2 API/сервер (уже реализованы)
|
|
||||||
- `ListSubscriptionsFeed`
|
|
||||||
- `GetChannelMessages`
|
|
||||||
- `GetMessageThread`
|
|
||||||
- `AddBlock`
|
|
||||||
|
|
||||||
#### 2.3 Тесты
|
|
||||||
- Есть интеграционный тест API каналов: `IT_06_ChannelsApi`.
|
|
||||||
- Есть тесты генерации блоков каналов/связей: `IT_03_AddBlock_NoAuth`.
|
|
||||||
- Формат `AddBlock` и его сборка/подпись описаны в `AddBlockSender`.
|
|
||||||
|
|
||||||
### 3. Проблемы текущей реализации (что надо закрыть)
|
|
||||||
- Кнопки «подписаться на человека/канал» в списке каналов сейчас UI-only (модалка без реальной записи через `AddBlock`).
|
|
||||||
- `add-channel-view` пока не создает канал на сервере через `AddBlock` (`CreateChannelBody`), только делает `navigate`.
|
|
||||||
- `channel-view` добавляет пост локально (в память), а не отправляет блок `TEXT_POST` через `AddBlock`.
|
|
||||||
- Нет полноценного экрана треда сообщения с реальными `GetMessageThread` и действиями `ответить/лайк/убрать лайк` через блоки.
|
|
||||||
- Нет гарантированного отображения канала в требуемом формате `ownerLogin/channelName`.
|
|
||||||
|
|
||||||
### 4. Функциональные требования
|
|
||||||
|
|
||||||
#### 4.1 Список каналов
|
|
||||||
На вкладке «Каналы» отображать 3 группы:
|
|
||||||
- Мои каналы
|
|
||||||
- Каналы пользователей, на кого я подписан
|
|
||||||
- Каналы, на которые я подписан
|
|
||||||
|
|
||||||
Источник данных: `ListSubscriptionsFeed`.
|
|
||||||
|
|
||||||
Каждый канал показывать в формате:
|
|
||||||
- `ownerLogin/channelName`
|
|
||||||
|
|
||||||
#### 4.2 Открытие канала
|
|
||||||
При входе в канал:
|
|
||||||
- загрузить сообщения через `GetChannelMessages`;
|
|
||||||
- показать список сообщений в хронологическом порядке (по текущему параметру `sort`);
|
|
||||||
- оставить техническую особенность непрочитанных как есть.
|
|
||||||
|
|
||||||
#### 4.3 Открытие треда сообщения
|
|
||||||
При клике на сообщение:
|
|
||||||
- загрузить тред через `GetMessageThread`;
|
|
||||||
- показать `ancestors`, `focus`, `descendants`;
|
|
||||||
- из треда должны быть доступны действия:
|
|
||||||
- «Ответить»
|
|
||||||
- «Лайк»
|
|
||||||
- «Убрать лайк»
|
|
||||||
|
|
||||||
Запись действий — только `AddBlock`.
|
|
||||||
|
|
||||||
#### 4.4 Создание канала
|
|
||||||
В `add-channel-view` кнопка «Создать» должна:
|
|
||||||
- отправлять `AddBlock` с телом `CreateChannelBody`;
|
|
||||||
- после успеха возвращать к списку каналов и обновлять его.
|
|
||||||
|
|
||||||
#### 4.5 Подписки
|
|
||||||
- Подписка на пользователя: `AddBlock` с `ConnectionBody` подтип `CONNECTION_FOLLOW`, target = HEADER пользователя.
|
|
||||||
- Подписка на канал: `AddBlock` с `ConnectionBody` подтип `CONNECTION_FOLLOW`, target = root блока канала (`CreateChannelBody` или HEADER для канала `0`).
|
|
||||||
|
|
||||||
### 5. API (форматы)
|
|
||||||
|
|
||||||
## 5.1 ListSubscriptionsFeed (чтение)
|
|
||||||
Request:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "ListSubscriptionsFeed",
|
|
||||||
"requestId": "...",
|
|
||||||
"payload": {
|
|
||||||
"login": "A1",
|
|
||||||
"limit": 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (смысловые поля):
|
|
||||||
- `ownedChannels[]`
|
|
||||||
- `followedUsersChannels[]`
|
|
||||||
- `followedChannels[]`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.2 GetChannelMessages (чтение)
|
|
||||||
Request:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "GetChannelMessages",
|
|
||||||
"requestId": "...",
|
|
||||||
"payload": {
|
|
||||||
"channel": {
|
|
||||||
"ownerBlockchainName": "A1-001",
|
|
||||||
"channelRootBlockNumber": 0,
|
|
||||||
"channelRootBlockHash": ""
|
|
||||||
},
|
|
||||||
"limit": 200,
|
|
||||||
"sort": "asc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (смысловые поля):
|
|
||||||
- `channel`
|
|
||||||
- `messages[]`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.3 GetMessageThread (чтение)
|
|
||||||
Request:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "GetMessageThread",
|
|
||||||
"requestId": "...",
|
|
||||||
"payload": {
|
|
||||||
"message": {
|
|
||||||
"blockchainName": "A1-001",
|
|
||||||
"blockNumber": 15,
|
|
||||||
"blockHash": "..."
|
|
||||||
},
|
|
||||||
"depthUp": 20,
|
|
||||||
"depthDown": 2,
|
|
||||||
"limitChildrenPerNode": 50
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (смысловые поля):
|
|
||||||
- `ancestors[]`
|
|
||||||
- `focus`
|
|
||||||
- `descendants[]`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.4 AddBlock (запись)
|
|
||||||
Любое изменение (создать канал, пост, reply, реакция, подписка) записывается через:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "AddBlock",
|
|
||||||
"requestId": "...",
|
|
||||||
"payload": {
|
|
||||||
"blockchainName": "A1-001",
|
|
||||||
"blockNumber": 6,
|
|
||||||
"prevBlockHash": "<64-hex>",
|
|
||||||
"blockBytesB64": "<base64 full block>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Важно:
|
|
||||||
- `blockBytesB64` формируется на клиенте.
|
|
||||||
- Подпись блока формируется на клиенте приватным blockchain key пользователя.
|
|
||||||
- Перед добавлением блока клиент берет актуальный курсор цепочки с сервера.
|
|
||||||
|
|
||||||
### 6. Типы блоков для каналов и связей (через AddBlock)
|
|
||||||
- Создание канала: `CreateChannelBody`
|
|
||||||
- Пост/ответ: `TextBody` (`TEXT_POST`, `TEXT_REPLY`)
|
|
||||||
- Реакции: `ReactionBody` (лайк/снятие лайка)
|
|
||||||
- Подписки: `ConnectionBody` (`CONNECTION_FOLLOW`)
|
|
||||||
|
|
||||||
### 7. Критерии приемки
|
|
||||||
- Список каналов отображается с реальными данными API.
|
|
||||||
- Формат названия канала в UI: `ownerLogin/channelName`.
|
|
||||||
- Создание канала реально пишет блок и канал появляется после обновления.
|
|
||||||
- Отправка поста/ответа/реакций реально пишет блок и видна после перечитки API.
|
|
||||||
- Подписка на пользователя/канал реально пишет блок и отражается в выдаче.
|
|
||||||
- Переход в тред сообщения показывает реальные `ancestors/focus/descendants`.
|
|
||||||
- Непрочитанные в списке каналов = общее число сообщений (временное правило).
|
|
||||||
|
|
||||||
### 8. Локальный запуск (уже сделано)
|
|
||||||
Команда:
|
|
||||||
```bash
|
|
||||||
./gradlew startLocal
|
|
||||||
```
|
|
||||||
|
|
||||||
Что делает:
|
|
||||||
- чистит логи;
|
|
||||||
- билдит сервер;
|
|
||||||
- запускает локальный WS сервер;
|
|
||||||
- запускает локальный HTTP сервер клиента;
|
|
||||||
- открывает браузер по URL с параметром `localWsPort`.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Краткое описание задачи
|
|
||||||
|
|
||||||
Нужно сделать полностью рабочую вкладку «Каналы» в SHiNE.
|
|
||||||
|
|
||||||
Пользователь должен:
|
|
||||||
- видеть список каналов;
|
|
||||||
- открывать канал и читать сообщения;
|
|
||||||
- открывать тред сообщения;
|
|
||||||
- отвечать, ставить и убирать лайк;
|
|
||||||
- подписываться на пользователей и каналы.
|
|
||||||
|
|
||||||
Чтение данных идет через 3 API:
|
|
||||||
- `ListSubscriptionsFeed`
|
|
||||||
- `GetChannelMessages`
|
|
||||||
- `GetMessageThread`
|
|
||||||
|
|
||||||
Все действия записи делаются только через `AddBlock` с подписью на клиенте.
|
|
||||||
|
|
||||||
Формат имени канала в интерфейсе:
|
|
||||||
- `имя_пользователя/имя_канала`
|
|
||||||
|
|
||||||
Локальный запуск проекта:
|
|
||||||
```bash
|
|
||||||
./gradlew startLocal
|
|
||||||
```
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
# Задача 02: Web Push + подписанный API отправки личных сообщений
|
|
||||||
|
|
||||||
## Контекст (по текущему состоянию проекта)
|
|
||||||
- Уже есть JSON WebSocket API для личных сообщений: `SendDirectMessage`, `AckIncomingMessage`, `UpsertPushToken`.
|
|
||||||
- Сейчас серверный fallback-пуш реализован через FCM (`FcmPushSender`) и ключ `fcm.server.key`.
|
|
||||||
- Клиент уже регистрирует service worker и токен Firebase, затем аплоадит push token на сервер.
|
|
||||||
|
|
||||||
## Цель
|
|
||||||
Добавить полностью рабочий сценарий доставки личных сообщений с приоритетом:
|
|
||||||
1) онлайн-доставка в активную WebSocket-сессию;
|
|
||||||
2) если не подтверждено — Web Push;
|
|
||||||
3) поддержать отдельный API отправки без авторизации, где доступ проверяется цифровой подписью Ed25519 по `clientKey` отправителя.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Предварительная спецификация подписанного пакета (v1)
|
|
||||||
> ВАЖНО: финально фиксируется после уточнений по endian/кодировкам/лимитам.
|
|
||||||
|
|
||||||
Пакет (binary):
|
|
||||||
1. `prefix` — ASCII-константа, например `SHINE_MESSAGE`.
|
|
||||||
2. `toLoginLen` — 1 байт.
|
|
||||||
3. `toLogin` — ASCII, длина = `toLoginLen`.
|
|
||||||
4. `fromLoginLen` — 1 байт.
|
|
||||||
5. `fromLogin` — ASCII, длина = `fromLoginLen`.
|
|
||||||
6. `timeMs` — 8 байт (unix ms).
|
|
||||||
7. `nonce32` — 4 байта случайное число.
|
|
||||||
8. `messageType` — 4 байта.
|
|
||||||
9. `targetMode` — 1 байт:
|
|
||||||
- `0` = всем сессиям пользователя,
|
|
||||||
- `1` = конкретной сессии.
|
|
||||||
10. Если `targetMode=1`:
|
|
||||||
- `sessionIdLen` — 1 байт,
|
|
||||||
- `sessionId` — ASCII.
|
|
||||||
11. `messageLen` — 2 байта.
|
|
||||||
12. `messageBytes` — бинарные данные длиной `messageLen`.
|
|
||||||
13. `signature64` — 64 байта, Ed25519 подпись всего блока **без** `signature64`.
|
|
||||||
|
|
||||||
Ограничения (первичный draft):
|
|
||||||
- общий размер пакета ≤ 4000 байт;
|
|
||||||
- логины/префикс/идентификатор сессии — ASCII;
|
|
||||||
- повторы отсекаются по `(fromLogin, timeMs, nonce32)` в окне TTL.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Сервер: что доработать
|
|
||||||
|
|
||||||
### 1) Новый endpoint без авторизации
|
|
||||||
Операция (через WS JSON обертку) условно `SendSignedDirectMessage`:
|
|
||||||
- принимает пакет (base64 binary blob);
|
|
||||||
- парсит и валидирует формат;
|
|
||||||
- достает `fromLogin`, поднимает `clientKey` пользователя;
|
|
||||||
- проверяет подпись Ed25519;
|
|
||||||
- проверяет анти-replay (time window + nonce);
|
|
||||||
- отправляет сообщение по правилам маршрутизации;
|
|
||||||
- пишет результат (messageId, каналы доставки, причины недоставки).
|
|
||||||
|
|
||||||
### 2) Маршрутизация доставки
|
|
||||||
Для `targetMode=1`:
|
|
||||||
- если целевая сессия онлайн и ACK пришел вовремя — успех;
|
|
||||||
- иначе отправка в Web Push этой сессии (если есть subscription).
|
|
||||||
|
|
||||||
Для `targetMode=0`:
|
|
||||||
- обход всех сессий пользователя;
|
|
||||||
- сначала online delivery + ACK;
|
|
||||||
- для непринятых/офлайн — Web Push по соответствующим subscription;
|
|
||||||
- если subscription отсутствует — тихий skip.
|
|
||||||
|
|
||||||
### 3) Миграция от FCM к Web Push
|
|
||||||
- добавить конфиг VAPID (`webpush.public.key`, `webpush.private.key`, `webpush.subject`);
|
|
||||||
- хранить на сервере не только token, а web-push subscription (endpoint + keys);
|
|
||||||
- сделать отправщик Web Push и заменить/расширить текущий `FcmPushSender`.
|
|
||||||
|
|
||||||
### 4) Безопасность
|
|
||||||
- строгая ASCII-валидация логинов/sessionId;
|
|
||||||
- лимиты длины всех полей;
|
|
||||||
- rate limit на endpoint;
|
|
||||||
- audit-лог неуспешных проверок подписи/формата;
|
|
||||||
- защита от replay.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Клиент (shine-UI): что доработать
|
|
||||||
|
|
||||||
1. Перейти на стандартный Web Push flow:
|
|
||||||
- регистрация service worker;
|
|
||||||
- `PushManager.subscribe(...)` с VAPID public key;
|
|
||||||
- отправка subscription на сервер (`UpsertPushSubscription` или расширение `UpsertPushToken`).
|
|
||||||
|
|
||||||
2. Service worker:
|
|
||||||
- `push` handler получает payload целиком;
|
|
||||||
- показывает системное уведомление;
|
|
||||||
- при клике открывает/фокусирует нужный чат.
|
|
||||||
|
|
||||||
3. Online-сообщения:
|
|
||||||
- сохранить текущий event-канал `IncomingDirectMessage`;
|
|
||||||
- обязателен ACK (`AckIncomingMessage` уже есть).
|
|
||||||
|
|
||||||
4. Keep-alive:
|
|
||||||
- UI отправляет `Ping` раз в 60 секунд при активной сессии.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Документация
|
|
||||||
Сделать отдельный документ настройки Web Push:
|
|
||||||
- как сгенерировать VAPID ключи;
|
|
||||||
- какие параметры прописать на сервере и в UI;
|
|
||||||
- как проверить локально e2e (онлайн + офлайн пуш);
|
|
||||||
- ограничения payload и рекомендации по ретраям.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Этапы реализации (предложение)
|
|
||||||
1. Зафиксировать бинарный формат + валидации.
|
|
||||||
2. Реализовать серверный parser/validator/signature verify/replay guard.
|
|
||||||
3. Реализовать Web Push sender + storage subscription.
|
|
||||||
4. Подключить новый endpoint и маршрутизацию доставки.
|
|
||||||
5. Обновить UI (subscription + service worker + ping timer).
|
|
||||||
6. Добавить интеграционные тесты (online ACK / offline push / bad signature / replay / oversize).
|
|
||||||
7. Добавить документацию.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Что нужно уточнить до разработки
|
|
||||||
1. Endian для `timeMs/nonce/messageType/messageLen` (big-endian или little-endian).
|
|
||||||
2. Что именно подписывается: строго весь префикс..messageBytes (без подписи) — подтвердить.
|
|
||||||
3. Диапазон допустимых `messageType`.
|
|
||||||
4. TTL окна для анти-replay (например 5 минут / 15 минут).
|
|
||||||
5. Лимиты длин для login/session/message.
|
|
||||||
6. Можно ли временно оставить FCM как fallback, пока не готов Web Push в проде.
|
|
||||||
7. Формат сообщения в `messageBytes`: opaque bytes или UTF-8 строка.
|
|
||||||
|
|
||||||
|
|
||||||
## Статус реализации (12.04.2026)
|
|
||||||
|
|
||||||
### Что уже внедрено в коде
|
|
||||||
- `SendDirectMessage` переведён на signed-binary payload (`blobB64`) без обязательной авторизации WS-сессии.
|
|
||||||
- Внедрён бинарный парсер пакета формата `SHiNE_msg + version(1) + ... + signature64`.
|
|
||||||
- Проверка подписи Ed25519 делается по `clientKey` отправителя через `shine-server-crypto` (`Ed25519Util`).
|
|
||||||
- Добавлен anti-replay guard `(from_login, time_ms, nonce)` с TTL 15 минут.
|
|
||||||
- Добавлено историческое хранилище `signed_direct_messages_history` с сырым пакетом `raw_packet`.
|
|
||||||
- Логика доставки: сначала WS+ACK, затем fallback на Web Push (по подписке конкретной session).
|
|
||||||
- Поле типа сообщения переведено на `uint16`, пока поддерживается только `1`.
|
|
||||||
- Для `targetMode=1` при несуществующей сессии возвращается `success` с `sessionNotFound=true` и `delivered=0`.
|
|
||||||
- UI переведён с Firebase/FCM на браузерный `PushManager.subscribe` + Service Worker `push`.
|
|
||||||
- Добавлен keep-alive ping из UI раз в 60 секунд при авторизованной сессии.
|
|
||||||
|
|
||||||
### Что настроить в окружении
|
|
||||||
- В `application.properties` задать:
|
|
||||||
- `webpush.vapid.public`
|
|
||||||
- `webpush.vapid.private`
|
|
||||||
- `webpush.vapid.subject`
|
|
||||||
- В `shine-UI/index.html` задать публичный VAPID ключ в `window.__SHINE_WEBPUSH_VAPID_PUBLIC_KEY__`.
|
|
||||||
|
|
||||||
@@ -36,4 +36,4 @@
|
|||||||
## Какие документы потом обновить
|
## Какие документы потом обновить
|
||||||
|
|
||||||
- `Deploy/`;
|
- `Deploy/`;
|
||||||
- `Dev_Docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления.
|
- `docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления.
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ QR-подключение других устройств сейчас есть
|
|||||||
|
|
||||||
## Какие документы потом обновить
|
## Какие документы потом обновить
|
||||||
|
|
||||||
- `Dev_Docs/Solana_Architecture/README.md`;
|
- `docs/Solana_Architecture/README.md`;
|
||||||
- `TODO/medium/2026-06-03_подключение_других_устройств_через_qr.md`;
|
- `TODO/medium/2026-06-03_подключение_других_устройств_через_qr.md`;
|
||||||
- `TODO/medium/2026-06-02_сессионные_homeserver_в_pda.md`.
|
- `TODO/medium/2026-06-02_сессионные_homeserver_в_pda.md`.
|
||||||
|
|||||||
+17
-3
@@ -12,16 +12,30 @@
|
|||||||
- откуда продолжать;
|
- откуда продолжать;
|
||||||
- какие документы потом надо обновить.
|
- какие документы потом надо обновить.
|
||||||
- Это не активная разработка. Тут только план и контекст.
|
- Это не активная разработка. Тут только план и контекст.
|
||||||
- Старую папку `Dev_Docs/Future_Features/` считать архивной и больше не использовать как источник новых задач.
|
- Старую папку `docs/Future_Features/` считать архивной и больше не использовать как источник новых задач.
|
||||||
|
|
||||||
## Текущие задачи
|
## Текущие задачи
|
||||||
|
|
||||||
- `2026-06-26_1800_корректное_завершение_за_30с.md` - дать сервису до 30 секунд на корректное завершение опасных операций перед рестартом.
|
- `2026-06-26_1800_корректное_завершение_за_30с.md` - дать сервису до 30 секунд на корректное завершение опасных операций перед рестартом.
|
||||||
- `2026-06-26_1805_межсерверный_ws_и_dm_sync.md` - постоянный server-to-server WebSocket, push новых блоков и DM, ACK и backfill.
|
|
||||||
- `2026-06-26_1810_подключение_устройств_по_qr.md` - довести подключение других устройств по QR и перевести это в нормальные типизированные сессии.
|
- `2026-06-26_1810_подключение_устройств_по_qr.md` - довести подключение других устройств по QR и перевести это в нормальные типизированные сессии.
|
||||||
- `2026-06-26_1815_esp32_файловое_хранилище.md` - использовать ESP32 как личное файловое хранилище для переписок и вложений.
|
- `2026-06-26_1815_esp32_файловое_хранилище.md` - использовать ESP32 как личное файловое хранилище для переписок и вложений.
|
||||||
|
|
||||||
## Перенесённые планы из `Dev_Docs/Future_Features/`
|
## Децентрализация
|
||||||
|
|
||||||
|
Текущий production-режим SHiNE считается односерверным. Задачи по нескольким серверам, Arweave и realtime PDA/Solana sync вынесены в `Децентрализация/` и не блокируют выкладку текущей версии на GitHub.
|
||||||
|
|
||||||
|
- `Децентрализация/односерверный_production_режим.md` - границы текущей production-версии с одним сервером.
|
||||||
|
- `Децентрализация/запись_блокчейнов_в_arweave.md` - будущая запись/архивация блокчейнов в Arweave.
|
||||||
|
- `Децентрализация/realtime_pda_solana_sync.md` - будущая онлайн-синхронизация PDA и Solana.
|
||||||
|
- `Децентрализация/межсерверная_передача_сообщений.md` - будущая доставка сообщений между серверами.
|
||||||
|
- `Децентрализация/межсерверные_звонки.md` - будущая маршрутизация звонков между серверами.
|
||||||
|
- `Децентрализация/2026-06-26_1805_межсерверный_ws_и_dm_sync.md` - перенесённый старый план постоянного server-to-server WS и DM sync.
|
||||||
|
|
||||||
|
## Новые фишки которые надо доделать
|
||||||
|
|
||||||
|
- `Новые фишки которые надо доделать/Новая_контентная_модель_блокчейна/` - отложенная новая контентная модель блокчейна, не входящая в текущий односерверный production-релиз.
|
||||||
|
|
||||||
|
## Перенесённые планы из `docs/Future_Features/`
|
||||||
|
|
||||||
### near
|
### near
|
||||||
|
|
||||||
|
|||||||
@@ -104,9 +104,9 @@
|
|||||||
|
|
||||||
## Какие документы нужно будет обновить при реализации
|
## Какие документы нужно будет обновить при реализации
|
||||||
|
|
||||||
- `Dev_Docs/Blockchain/README.md` и связанные файлы, если изменятся типы служебных сообщений или форматы блокчейн-команд.
|
- `docs/Blockchain/README.md` и связанные файлы, если изменятся типы служебных сообщений или форматы блокчейн-команд.
|
||||||
- `Dev_Docs/API/` если изменится публичный серверный API или появятся новые операции.
|
- `docs/API/` если изменится публичный серверный API или появятся новые операции.
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий.
|
- `docs/Personal_Messages/Протокол_DM_v1.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий.
|
||||||
- Документацию по homeserver/ESP32, если появится пользовательская или сервисная файловая логика на устройстве.
|
- Документацию по homeserver/ESP32, если появится пользовательская или сервисная файловая логика на устройстве.
|
||||||
|
|
||||||
## С какого места продолжать позже
|
## С какого места продолжать позже
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
## Почему это не лежит в Pending_Features
|
## Почему это не лежит в Pending_Features
|
||||||
|
|
||||||
`Dev_Docs/Pending_Features/` предназначена для фич, которые уже реализованы и ждут ручной проверки.
|
`docs/Pending_Features/` предназначена для фич, которые уже реализованы и ждут ручной проверки.
|
||||||
|
|
||||||
Репосты сейчас не подходят под этот статус: они не должны проверяться как готовая фича, потому что пользовательский сценарий временно закрыт, а серверная запись новых репостов заблокирована. Поэтому старый pending-файл удалён, а задача перенесена сюда как будущая.
|
Репосты сейчас не подходят под этот статус: они не должны проверяться как готовая фича, потому что пользовательский сценарий временно закрыт, а серверная запись новых репостов заблокирована. Поэтому старый pending-файл удалён, а задача перенесена сюда как будущая.
|
||||||
|
|
||||||
@@ -82,11 +82,11 @@
|
|||||||
- отображение `targetBlockchainName`, `targetBlockNumber`, `targetBlockHash`.
|
- отображение `targetBlockchainName`, `targetBlockNumber`, `targetBlockHash`.
|
||||||
7. Добавить или обновить тесты на успешный репост и отказ некорректных target-полей.
|
7. Добавить или обновить тесты на успешный репост и отказ некорректных target-полей.
|
||||||
8. Обновить документацию:
|
8. Обновить документацию:
|
||||||
- `Dev_Docs/Blockchain/11_TEXT_Blocks.md`;
|
- `docs/Blockchain/11_TEXT_Blocks.md`;
|
||||||
- `Dev_Docs/Blockchain/CHANGELOG.md`;
|
- `docs/Blockchain/CHANGELOG.md`;
|
||||||
- `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`;
|
- `docs/API/04_Add_Block_to_Blockchain_API.md`;
|
||||||
- документы API чтения каналов/тредов, если изменятся поля ответа.
|
- документы API чтения каналов/тредов, если изменятся поля ответа.
|
||||||
9. После реализации перенести задачу из `TODO/` в `Dev_Docs/Pending_Features/` как фичу, требующую ручной проверки.
|
9. После реализации перенести задачу из `TODO/` в `docs/Pending_Features/` как фичу, требующую ручной проверки.
|
||||||
|
|
||||||
## Минимальный чек-лист ручной проверки в будущем
|
## Минимальный чек-лист ручной проверки в будущем
|
||||||
|
|
||||||
|
|||||||
@@ -47,10 +47,10 @@
|
|||||||
|
|
||||||
## Документы, которые обновить при реализации
|
## Документы, которые обновить при реализации
|
||||||
|
|
||||||
- `Dev_Docs/Blockchain/`, если появятся или изменятся блоки баланса.
|
- `docs/Blockchain/`, если появятся или изменятся блоки баланса.
|
||||||
- `Dev_Docs/Blockchain/CHANGELOG.md`, если меняется блокчейн-формат.
|
- `docs/Blockchain/CHANGELOG.md`, если меняется блокчейн-формат.
|
||||||
- `Dev_Docs/API/`, если меняется серверный API.
|
- `docs/API/`, если меняется серверный API.
|
||||||
- `Dev_Docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
- `docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
||||||
- Документацию Solana-регистрации, если баланс будет связан с Solana-модулем.
|
- Документацию Solana-регистрации, если баланс будет связан с Solana-модулем.
|
||||||
|
|
||||||
## Минимальная проверка в будущем
|
## Минимальная проверка в будущем
|
||||||
|
|||||||
@@ -34,10 +34,10 @@
|
|||||||
|
|
||||||
## Документы, которые нужно обновить при возврате
|
## Документы, которые нужно обновить при возврате
|
||||||
|
|
||||||
- `Dev_Docs/Keys/README.md`
|
- `docs/Keys/README.md`
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
- `docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
- `Dev_Docs/API/`
|
- `docs/API/`
|
||||||
- `Dev_Docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
- `docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
||||||
|
|
||||||
## С какого места продолжать
|
## С какого места продолжать
|
||||||
|
|
||||||
|
|||||||
@@ -84,11 +84,11 @@
|
|||||||
## Что нужно обновить при реализации
|
## Что нужно обновить при реализации
|
||||||
|
|
||||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
||||||
- `Dev_Docs/Solana_Architecture/README.md`
|
- `docs/Solana_Architecture/README.md`
|
||||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
- `docs/Инициализация_Solana_регистрации/README.md`
|
||||||
- `Dev_Docs/Keys/README.md`
|
- `docs/Keys/README.md`
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`, если изменится адресация DM по типам сессий
|
- `docs/Personal_Messages/Протокол_DM_v1.md`, если изменится адресация DM по типам сессий
|
||||||
- `Dev_Docs/API/`, если появятся новые серверные операции или изменятся ответы
|
- `docs/API/`, если появятся новые серверные операции или изменятся ответы
|
||||||
|
|
||||||
## Что пока не делать
|
## Что пока не делать
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
## Что обновить при возврате
|
## Что обновить при возврате
|
||||||
|
|
||||||
- `Dev_Docs/Pending_Features/README.md`
|
- `docs/Pending_Features/README.md`
|
||||||
- `shine-UI/js/pages/connect-device-view.js`
|
- `shine-UI/js/pages/connect-device-view.js`
|
||||||
- `shine-UI/js/pages/device-qr-view.js`
|
- `shine-UI/js/pages/device-qr-view.js`
|
||||||
- `shine-UI/js/services/qr-key-transfer-service.js`
|
- `shine-UI/js/services/qr-key-transfer-service.js`
|
||||||
|
|||||||
@@ -58,8 +58,8 @@
|
|||||||
## Документы, которые обновить при реализации
|
## Документы, которые обновить при реализации
|
||||||
|
|
||||||
- Документацию UI/кошельков, если такая есть.
|
- Документацию UI/кошельков, если такая есть.
|
||||||
- `Dev_Docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
- `docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
||||||
- `Dev_Docs/API/`, только если появится новый серверный API или логирование.
|
- `docs/API/`, только если появится новый серверный API или логирование.
|
||||||
|
|
||||||
## Минимальная проверка
|
## Минимальная проверка
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ git show 0240db5:shine-solana/shine/programs/shine_login_guard/src/lib.rs
|
|||||||
- `shine-solana/shine/doc/programs/shine_login_guard.md`
|
- `shine-solana/shine/doc/programs/shine_login_guard.md`
|
||||||
|
|
||||||
5. Архитектурная документация:
|
5. Архитектурная документация:
|
||||||
- `Dev_Docs/Solana_Architecture/README.md`
|
- `docs/Solana_Architecture/README.md`
|
||||||
|
|
||||||
6. UI-логика precheck:
|
6. UI-логика precheck:
|
||||||
- `shine-UI/js/pages/register-view.js`
|
- `shine-UI/js/pages/register-view.js`
|
||||||
@@ -108,7 +108,7 @@ git show 0240db5:shine-solana/shine/programs/shine_login_guard/src/lib.rs
|
|||||||
4. Проверить, что `build.rs` всё ещё генерирует `generated_dictionary.rs` в прежнем формате.
|
4. Проверить, что `build.rs` всё ещё генерирует `generated_dictionary.rs` в прежнем формате.
|
||||||
5. Сверить актуальность словарей в `src/dictionaries`.
|
5. Сверить актуальность словарей в `src/dictionaries`.
|
||||||
6. Обновить `shine_login_guard.md` обратно под словарную логику.
|
6. Обновить `shine_login_guard.md` обратно под словарную логику.
|
||||||
7. Обновить `Dev_Docs/Solana_Architecture/README.md`.
|
7. Обновить `docs/Solana_Architecture/README.md`.
|
||||||
|
|
||||||
### Проверка после возврата
|
### Проверка после возврата
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,6 @@
|
|||||||
|
|
||||||
## Какие документы потом обновить
|
## Какие документы потом обновить
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`, если изменится стратегия миграции старой истории;
|
- `docs/Personal_Messages/Протокол_DM_v1.md`, если изменится стратегия миграции старой истории;
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`, если появится отдельное правило совместимости/конвертации;
|
- `docs/Personal_Messages/Формат_DM_v1.md`, если появится отдельное правило совместимости/конвертации;
|
||||||
- при необходимости `Dev_Docs/API/12_Direct_Messages_Push_Calls_API.md`, если затронется поведение backlog/доставки.
|
- при необходимости `docs/API/12_Direct_Messages_Push_Calls_API.md`, если затронется поведение backlog/доставки.
|
||||||
|
|||||||
+7
-5
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
## Зачем
|
## Зачем
|
||||||
|
|
||||||
Сейчас синхронизация между серверами работает в основном как periodic sync и one-shot push. Для нормальной репликации ещё нужен постоянный межсерверный канал:
|
Текущий production-режим SHiNE рассчитан на один основной сервер. Межсерверная синхронизация относится к будущей децентрализации и не должна блокировать выкладку односерверной production-версии.
|
||||||
|
|
||||||
|
Сейчас синхронизация между серверами работает в основном как periodic sync и one-shot push. Для нормальной репликации в будущем ещё нужен постоянный межсерверный канал:
|
||||||
|
|
||||||
- живое подключение к партнёру;
|
- живое подключение к партнёру;
|
||||||
- push новых блоков;
|
- push новых блоков;
|
||||||
@@ -35,7 +37,7 @@
|
|||||||
|
|
||||||
## Какие документы потом обновить
|
## Какие документы потом обновить
|
||||||
|
|
||||||
- `Dev_Docs/Blockchain/sync-between-servers.md`;
|
- `docs/Blockchain/sync-between-servers.md`;
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`;
|
- `docs/Personal_Messages/Протокол_DM_v1.md`;
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`;
|
- `docs/Personal_Messages/Формат_DM_v1.md`;
|
||||||
- `Dev_Docs/API/`.
|
- `docs/API/`.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Децентрализация
|
||||||
|
|
||||||
|
Папка для задач, которые нужны для будущего режима с несколькими серверами, Solana/PDA-синхронизацией и внешним хранением данных.
|
||||||
|
|
||||||
|
## Текущий статус
|
||||||
|
|
||||||
|
Сейчас production-режим SHiNE считается односерверным: один сервер обслуживает пользователей, сообщения, звонки и запись данных. Задачи из этой папки не являются блокерами для выкладки текущего репозитория на GitHub и запуска одного production-сервера.
|
||||||
|
|
||||||
|
## Задачи
|
||||||
|
|
||||||
|
- `односерверный_production_режим.md` - зафиксировать границы текущей production-версии.
|
||||||
|
- `запись_блокчейнов_в_arweave.md` - вынести долговременную запись блокчейнов в Arweave.
|
||||||
|
- `realtime_pda_solana_sync.md` - сделать онлайн-синхронизацию PDA/Solana в реальном времени.
|
||||||
|
- `межсерверная_передача_сообщений.md` - реализовать доставку сообщений между серверами.
|
||||||
|
- `межсерверные_звонки.md` - реализовать маршрутизацию звонков между серверами.
|
||||||
|
- `2026-06-26_1805_межсерверный_ws_и_dm_sync.md` - старый план постоянного server-to-server WS и DM sync, перенесённый в контекст децентрализации.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Realtime-синхронизация PDA и Solana
|
||||||
|
|
||||||
|
## Зачем
|
||||||
|
|
||||||
|
В будущем PDA-записи и Solana-состояние должны автоматически и быстро синхронизироваться с серверным состоянием, чтобы данные пользователей, homeserver-сессии и связанные записи не расходились.
|
||||||
|
|
||||||
|
## Что сделать
|
||||||
|
|
||||||
|
1. Определить, какие серверные события должны обновлять PDA.
|
||||||
|
2. Добавить очередь/воркер для надёжной отправки изменений в Solana.
|
||||||
|
3. Добавить периодическую сверку серверного состояния с PDA.
|
||||||
|
4. Добавить обработку ошибок, повторов и конфликтов версий.
|
||||||
|
5. Добавить мониторинг задержек и неуспешных Solana-транзакций.
|
||||||
|
|
||||||
|
## Что учесть
|
||||||
|
|
||||||
|
- Solana/Anchor-модуль находится в `shine-solana/shine/` и ведётся отдельно от основного server/UI deploy.
|
||||||
|
- Перед изменениями внутри Solana-модуля нужно читать `shine-solana/shine/AGENTS.md`.
|
||||||
|
- Основная инструкция по Solana-регистрации находится в `docs/Инициализация_Solana_регистрации/README.md`.
|
||||||
|
- Формат пользовательской PDA-записи описан в `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`.
|
||||||
|
|
||||||
|
## Документы, которые потом нужно обновить
|
||||||
|
|
||||||
|
- `docs/Инициализация_Solana_регистрации/README.md`;
|
||||||
|
- `docs/Solana_Architecture/README.md`;
|
||||||
|
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`, если меняется формат PDA.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Отложено до этапа децентрализации.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Запись блокчейнов в Arweave
|
||||||
|
|
||||||
|
## Зачем
|
||||||
|
|
||||||
|
Для будущей децентрализации нужно долговременное внешнее хранение блокчейнов, чтобы данные не зависели только от одного серверного диска.
|
||||||
|
|
||||||
|
## Что сделать
|
||||||
|
|
||||||
|
1. Определить, какие блокчейны и какие диапазоны блоков записываются в Arweave.
|
||||||
|
2. Зафиксировать формат пачки блоков, метаданных, ссылок и контрольных хэшей.
|
||||||
|
3. Добавить безопасный механизм публикации без хранения приватного JWK в git.
|
||||||
|
4. Добавить проверку уже загруженных диапазонов, чтобы не плодить дубли.
|
||||||
|
5. Описать восстановление блокчейна из Arweave при потере локальных данных.
|
||||||
|
|
||||||
|
## Важные ограничения
|
||||||
|
|
||||||
|
- Любое изменение формата блокчейна требует отдельного предупреждения и явного подтверждения пользователя.
|
||||||
|
- Добавление данных в блокчейн должно выполняться только через `AddBlock`.
|
||||||
|
- Секреты Arweave нельзя хранить в репозитории.
|
||||||
|
|
||||||
|
## Документы, которые потом нужно обновить
|
||||||
|
|
||||||
|
- `docs/Blockchain/README.md`;
|
||||||
|
- `docs/Blockchain/CHANGELOG.md`;
|
||||||
|
- документы deploy/секретов в `Deploy/`, если появятся новые параметры.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Отложено до этапа децентрализации.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Межсерверная передача сообщений
|
||||||
|
|
||||||
|
## Зачем
|
||||||
|
|
||||||
|
Когда у SHiNE появится несколько серверов, пользователи на разных серверах должны получать личные сообщения без ручной синхронизации и без привязки к одному центральному узлу.
|
||||||
|
|
||||||
|
## Что сделать
|
||||||
|
|
||||||
|
1. Определить протокол server-to-server доставки DM.
|
||||||
|
2. Добавить маршрутизацию получателя по серверу, user id, публичному ключу или PDA.
|
||||||
|
3. Добавить ACK, повторы, дедупликацию и backfill пропущенных сообщений.
|
||||||
|
4. Разделить realtime-доставку и восстановление истории.
|
||||||
|
5. Описать поведение при недоступности удалённого сервера.
|
||||||
|
|
||||||
|
## Что учесть
|
||||||
|
|
||||||
|
- Логика DM должна соответствовать документам в `docs/Personal_Messages/`.
|
||||||
|
- При изменении формата signed DM-блока или правил доставки нужно обновлять протокол и байтовый формат DM.
|
||||||
|
- Если появятся новые server API/WebSocket операции, нужно обновить `docs/API/`.
|
||||||
|
|
||||||
|
## Документы, которые потом нужно обновить
|
||||||
|
|
||||||
|
- `docs/Personal_Messages/Протокол_DM_v1.md`;
|
||||||
|
- `docs/Personal_Messages/Формат_DM_v1.md`;
|
||||||
|
- `docs/API/`;
|
||||||
|
- `docs/API/09_Operations_Index.md`, если добавляются новые `op`.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Отложено до этапа децентрализации.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Межсерверные звонки
|
||||||
|
|
||||||
|
## Зачем
|
||||||
|
|
||||||
|
В будущем пользователи на разных серверах должны иметь возможность устанавливать звонки так же, как пользователи одного сервера.
|
||||||
|
|
||||||
|
## Что сделать
|
||||||
|
|
||||||
|
1. Определить протокол межсерверной сигнализации звонков.
|
||||||
|
2. Добавить маршрутизацию offer/answer/ICE-кандидатов между серверами.
|
||||||
|
3. Добавить обработку статусов занятости, отказа, таймаута и ошибок маршрута.
|
||||||
|
4. Добавить диагностику доставки сигналов между серверами.
|
||||||
|
5. Проверить совместимость с текущими логами `CallDeliveryReport`.
|
||||||
|
|
||||||
|
## Что учесть
|
||||||
|
|
||||||
|
- Специальная диагностика установки звонков идёт через `CallDeliveryReport`.
|
||||||
|
- На production важно сохранять поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
|
||||||
|
- Межсерверные звонки не должны ломать текущий односерверный сценарий.
|
||||||
|
|
||||||
|
## Документы, которые потом нужно обновить
|
||||||
|
|
||||||
|
- `docs/API/`, если добавляются или меняются операции сигнализации;
|
||||||
|
- документы по звонкам/диагностике, если они будут выделены отдельно;
|
||||||
|
- deploy-документы, если появятся новые параметры TURN/server-to-server маршрутизации.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Отложено до этапа децентрализации.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Односерверный production-режим
|
||||||
|
|
||||||
|
## Зачем
|
||||||
|
|
||||||
|
Перед выкладкой репозитория на GitHub и запуском production нужно явно зафиксировать, что текущая стабильная версия работает как один основной сервер.
|
||||||
|
|
||||||
|
## Что считаем текущей нормой
|
||||||
|
|
||||||
|
- Один production-сервер обслуживает пользователей, сообщения, звонки и серверные данные.
|
||||||
|
- Децентрализованные сценарии не считаются обязательными для первого production-релиза.
|
||||||
|
- Межсерверная доставка сообщений, межсерверные звонки, realtime PDA/Solana sync и запись блокчейнов в Arweave вынесены в отдельные будущие задачи.
|
||||||
|
- Код и документация текущего production не должны создавать ожидание, что несколько серверов уже работают как единая realtime-сеть.
|
||||||
|
|
||||||
|
## Что сделать перед возвратом к децентрализации
|
||||||
|
|
||||||
|
1. Проверить актуальные документы по API, blockchain, DM и deploy.
|
||||||
|
2. Выделить минимальный протокол server-to-server взаимодействия.
|
||||||
|
3. Решить, какие данные остаются локальными, какие реплицируются между серверами, а какие записываются во внешнее долговременное хранилище.
|
||||||
|
4. После изменения API, blockchain-форматов или DM-протокола обновить соответствующие документы по правилам проекта.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Отложено. Текущий production работает как один сервер.
|
||||||
+275
@@ -0,0 +1,275 @@
|
|||||||
|
# Новая логика контента в блокчейне SHiNE
|
||||||
|
|
||||||
|
## Зачем это нужно
|
||||||
|
|
||||||
|
Сейчас блокчейн SHiNE хорошо умеет хранить обычные сообщения, ответы, лайки и связи между людьми.
|
||||||
|
|
||||||
|
Новая модель добавляет поверх этого более понятный смысл контента:
|
||||||
|
|
||||||
|
- обычный текст;
|
||||||
|
- упражнение;
|
||||||
|
- услуга / процедура;
|
||||||
|
- курс;
|
||||||
|
- стартовая страница канала (`entrypoint`).
|
||||||
|
|
||||||
|
Это нужно для того, чтобы канал стал не просто лентой постов, а полноценным пространством знаний, практик, услуг и сообществ.
|
||||||
|
|
||||||
|
## Что меняется для людей
|
||||||
|
|
||||||
|
### 1. В канале появятся понятные виды материалов
|
||||||
|
|
||||||
|
Сообщение можно будет создать не только как обычный текст, но и как:
|
||||||
|
|
||||||
|
- упражнение;
|
||||||
|
- услугу / процедуру;
|
||||||
|
- курс;
|
||||||
|
- стартовую страницу канала.
|
||||||
|
|
||||||
|
Смысл в том, что приложение и сервер будут понимать, что это за материал, а не просто показывать любой текст одинаково.
|
||||||
|
|
||||||
|
### 2. У канала будет стартовая страница
|
||||||
|
|
||||||
|
У канала появится отдельное стартовое сообщение `entrypoint`.
|
||||||
|
|
||||||
|
Это не курс и не оглавление, а именно главная точка входа в канал:
|
||||||
|
|
||||||
|
- короткое объяснение, о чём канал;
|
||||||
|
- описание структуры;
|
||||||
|
- ссылки на нужные материалы;
|
||||||
|
- удобное начало для новых людей.
|
||||||
|
|
||||||
|
У канала в каждый момент времени будет только одна актуальная стартовая страница.
|
||||||
|
Если её исправляют, то сохраняется история версий.
|
||||||
|
Если её удаляют, для интерфейса считается, что стартовой страницы у канала сейчас нет.
|
||||||
|
|
||||||
|
### 3. Курс, упражнение и услуга / процедура будут отличаться по смыслу
|
||||||
|
|
||||||
|
Это важно для логики и статистики.
|
||||||
|
|
||||||
|
- `Упражнение` — то, что человек может делать много раз.
|
||||||
|
- `Услуга / процедура` — то, что тоже можно проходить много раз, но обычно с участием другого человека.
|
||||||
|
- `Курс` — то, что можно начать, закончить или бросить.
|
||||||
|
|
||||||
|
За счёт этого сервер сможет честно считать активность, а интерфейс сможет показывать человеку именно те действия, которые подходят к данному типу материала.
|
||||||
|
|
||||||
|
### 4. Появятся статусные действия
|
||||||
|
|
||||||
|
На контент можно будет не только ответить или поставить лайк, но и отметить свой путь:
|
||||||
|
|
||||||
|
- сделал один раз;
|
||||||
|
- заинтересовался и рассматривает;
|
||||||
|
- начал;
|
||||||
|
- закончил / освоил / знаю;
|
||||||
|
- бросил.
|
||||||
|
|
||||||
|
При этом:
|
||||||
|
|
||||||
|
- для упражнений и услуг / процедур будет отдельно считаться, сколько раз человек сделал / прошёл;
|
||||||
|
- для упражнений и курсов будет храниться текущий статус.
|
||||||
|
|
||||||
|
Текущий статус определяется просто:
|
||||||
|
|
||||||
|
- последнее статусное действие и считается актуальным.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
- если последнее действие “заинтересовался и рассматривает”, значит человек присматривается, но ещё не начал;
|
||||||
|
- если последнее действие `started`, значит материал сейчас в процессе;
|
||||||
|
- если последнее действие `abandoned`, значит человек бросил;
|
||||||
|
- если последнее действие `completed`, значит для системы он завершил / освоил материал.
|
||||||
|
|
||||||
|
### 5. К действиям можно добавлять живой текст
|
||||||
|
|
||||||
|
Практически любое статусное действие можно будет сопровождать коротким комментарием.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
- “Начал изучать, потому что давно хотел разобраться”;
|
||||||
|
- “Бросил, пока нет времени”;
|
||||||
|
- “Прошёл процедуру, стало заметно легче”.
|
||||||
|
|
||||||
|
Это важно, потому что сам блокчейн будет хранить не только формальный статус, но и живую человеческую причину или заметку.
|
||||||
|
|
||||||
|
### 6. Появится подтверждение статуса другими людьми
|
||||||
|
|
||||||
|
Отдельный человек сможет подтвердить чей-то статус.
|
||||||
|
|
||||||
|
Примеры:
|
||||||
|
|
||||||
|
- подтвердить, что человек действительно занимался;
|
||||||
|
- подтвердить, что он реально прошёл услугу;
|
||||||
|
- подтвердить, что он освоил материал.
|
||||||
|
|
||||||
|
Подтверждение — это не замена статуса, а отдельное мнение / свидетельство со стороны.
|
||||||
|
|
||||||
|
### 7. Появится отдельный тип «мнение»
|
||||||
|
|
||||||
|
На любое сообщение можно будет ответить не только обычным ответом, но и специальным типом ответа: `мнение`.
|
||||||
|
|
||||||
|
Это по сути тоже текстовый ответ, но с отдельным смыслом:
|
||||||
|
|
||||||
|
- это отзыв;
|
||||||
|
- это оценка;
|
||||||
|
- это мнение о материале;
|
||||||
|
- это явная метка для будущего анализа нейронками.
|
||||||
|
|
||||||
|
То есть:
|
||||||
|
|
||||||
|
- обычный ответ нужен для разговора;
|
||||||
|
- `мнение` нужно для отзыва, оценки и анализа реакции людей.
|
||||||
|
|
||||||
|
## Что остаётся как раньше
|
||||||
|
|
||||||
|
### Комментарии
|
||||||
|
|
||||||
|
Обычные ответы на сообщения остаются.
|
||||||
|
То есть обсуждение материалов не ломается и не меняется концептуально.
|
||||||
|
|
||||||
|
### Лайки контента
|
||||||
|
|
||||||
|
Лайк на сообщение, курс, упражнение или услугу остаётся обычной реакцией на конкретный блок.
|
||||||
|
|
||||||
|
### Лайк пользователю
|
||||||
|
|
||||||
|
Лайк пользователю не будет считаться реакцией на сообщение.
|
||||||
|
Он относится к графу связей между людьми.
|
||||||
|
|
||||||
|
Это удобно, потому что:
|
||||||
|
|
||||||
|
- лайк человека — это отношение к человеку;
|
||||||
|
- лайк материала — это отношение к контенту.
|
||||||
|
|
||||||
|
## Сообщество вокруг канала
|
||||||
|
|
||||||
|
Канал сможет работать не только как лента, но и как сообщество.
|
||||||
|
|
||||||
|
Для этого появятся простые действия:
|
||||||
|
|
||||||
|
- заявка на вступление;
|
||||||
|
- самостоятельный выход;
|
||||||
|
- принятие;
|
||||||
|
- исключение.
|
||||||
|
|
||||||
|
Сервер сможет понимать:
|
||||||
|
|
||||||
|
- кто только подал заявку;
|
||||||
|
- кто уже принят;
|
||||||
|
- кто вышел;
|
||||||
|
- кто был исключён.
|
||||||
|
|
||||||
|
## Личный канал и лента достижений
|
||||||
|
|
||||||
|
У каждого человека по смыслу появляется два важных пространства:
|
||||||
|
|
||||||
|
- канал его обычных постов;
|
||||||
|
- отдельная лента его тренировок и достижений.
|
||||||
|
|
||||||
|
В обычном канале человек сможет:
|
||||||
|
|
||||||
|
- писать посты;
|
||||||
|
- делиться мыслями;
|
||||||
|
- публиковать материалы;
|
||||||
|
- обсуждать темы как раньше.
|
||||||
|
|
||||||
|
А в ленте достижений будут видны его реальные действия:
|
||||||
|
|
||||||
|
- какие упражнения он делал;
|
||||||
|
- какие услуги / процедуры проходил;
|
||||||
|
- какие курсы его заинтересовали;
|
||||||
|
- какие курсы он начал;
|
||||||
|
- какие курсы он закончил;
|
||||||
|
- что он бросил.
|
||||||
|
|
||||||
|
То есть блокчейн SHiNE сможет хранить не только слова человека, но и его путь, активность и историю практики.
|
||||||
|
|
||||||
|
## Что смогут делать авторы контента
|
||||||
|
|
||||||
|
Создатели контента в своих каналах смогут публиковать не только обычные посты, но и:
|
||||||
|
|
||||||
|
- упражнения;
|
||||||
|
- курсы;
|
||||||
|
- стартовую страницу канала;
|
||||||
|
- услуги / процедуры, которые они оказывают.
|
||||||
|
|
||||||
|
Это превращает канал в сочетание:
|
||||||
|
|
||||||
|
- блога;
|
||||||
|
- базы знаний;
|
||||||
|
- пространства обучения;
|
||||||
|
- каталога услуг и практик.
|
||||||
|
|
||||||
|
## Что увидит человек в интерфейсе
|
||||||
|
|
||||||
|
На специальных сообщениях в UI можно будет показывать отдельные кнопки действий.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
- `Выполнил упражнение`
|
||||||
|
- `Прошёл процедуру`
|
||||||
|
- `Заинтересовало`
|
||||||
|
- `Начал курс`
|
||||||
|
- `Закончил курс`
|
||||||
|
|
||||||
|
То есть материал можно будет не просто прочитать, а сразу отметить реальное действие.
|
||||||
|
|
||||||
|
Также при ответе на любое сообщение можно будет выбрать:
|
||||||
|
|
||||||
|
- обычный ответ;
|
||||||
|
- `мнение / отзыв`.
|
||||||
|
|
||||||
|
## Как будет работать лента достижений
|
||||||
|
|
||||||
|
Если кто-то зайдёт в твою ленту достижений, он сможет:
|
||||||
|
|
||||||
|
- прочитать, что ты делал;
|
||||||
|
- оставить мнение / отзыв;
|
||||||
|
- подтвердить, что это действительно было.
|
||||||
|
|
||||||
|
Это даёт основу для мягкой “сертификации” внутри SHiNE.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
- человек прошёл курс и получил подтверждения;
|
||||||
|
- человек прошёл процедуру и получил отзыв;
|
||||||
|
- человек регулярно делает упражнения, и это видно в его истории.
|
||||||
|
|
||||||
|
Так постепенно у пользователя появляется не только лента постов, но и лента достижений, подтверждений и репутации.
|
||||||
|
|
||||||
|
## Ссылки внутри SHiNE
|
||||||
|
|
||||||
|
Для переходов между материалами вводятся простые внутренние адреса:
|
||||||
|
|
||||||
|
- обычная ссылка: `SHiNE/alice-001/157`
|
||||||
|
- особополная ссылка: `SHiNE/alice-001/157/ХЭШ`
|
||||||
|
|
||||||
|
Первая форма — основная и каноническая.
|
||||||
|
Вторая нужна там, где хочется добавить ещё и точную проверку по хэшу.
|
||||||
|
|
||||||
|
## Что это даёт в итоге
|
||||||
|
|
||||||
|
После внедрения новая блокчейн-логика позволит:
|
||||||
|
|
||||||
|
- строить каналы как структурированные пространства, а не просто как поток постов;
|
||||||
|
- выделять упражнения, услуги и курсы как отдельные сущности;
|
||||||
|
- показывать стартовую страницу канала;
|
||||||
|
- хранить путь человека по материалу;
|
||||||
|
- хранить отдельную ленту его действий и достижений;
|
||||||
|
- считать активность и статусы;
|
||||||
|
- подтверждать результаты другими людьми;
|
||||||
|
- развивать сообщество вокруг канала.
|
||||||
|
|
||||||
|
И самое важное: всё это можно добавить как расширение уже существующего блокчейна SHiNE, не разрушая старую модель сообщений.
|
||||||
|
|
||||||
|
## Отдельный вопрос для будущего
|
||||||
|
|
||||||
|
Отзывы о людях как о людях — полезная идея, но её стоит дополнительно обдумать.
|
||||||
|
|
||||||
|
Например, на вкладке связей в будущем можно:
|
||||||
|
|
||||||
|
- писать человеку отзыв;
|
||||||
|
- смотреть все отзывы о человеке;
|
||||||
|
- выводить сначала отзывы близких друзей, родственников, друзей и контактов, а уже потом остальные.
|
||||||
|
|
||||||
|
Но этот слой нужно делать осторожно, чтобы он не стал слишком жёстким или неприятным для людей.
|
||||||
|
|
||||||
|
Поэтому отзывы о людях как отдельная социальная механика требуют дополнительного обсуждения и проектирования.
|
||||||
+670
@@ -0,0 +1,670 @@
|
|||||||
|
# ТЗ: новая контентная модель блокчейна SHiNE
|
||||||
|
|
||||||
|
## Статус документа
|
||||||
|
|
||||||
|
Этот документ описывает предлагаемые новые типы блоков и правила их обработки.
|
||||||
|
|
||||||
|
Цель:
|
||||||
|
|
||||||
|
- добавить новую семантику контента;
|
||||||
|
- не ломать существующие блоки `type=0..4`;
|
||||||
|
- внедрить всё как расширение блокчейна за счёт новых форматов.
|
||||||
|
|
||||||
|
Документ является проектным ТЗ на реализацию в сервере, БД, API чтения и UI.
|
||||||
|
|
||||||
|
## 1. Базовые принципы
|
||||||
|
|
||||||
|
### 1.1. Совместимость
|
||||||
|
|
||||||
|
Старые типы не меняются:
|
||||||
|
|
||||||
|
- `type=0` — TECH
|
||||||
|
- `type=1` — TEXT
|
||||||
|
- `type=2` — REACTION
|
||||||
|
- `type=3` — CONNECTION
|
||||||
|
- `type=4` — USER_PARAM
|
||||||
|
|
||||||
|
Новые сущности и действия добавляются только как новые `type` и новые `body`.
|
||||||
|
|
||||||
|
Это означает:
|
||||||
|
|
||||||
|
- старые блоки продолжают читаться как раньше;
|
||||||
|
- старые `TEXT_POST`, `TEXT_REPLY`, `REACTION_LIKE` и остальные форматы не ломаются;
|
||||||
|
- существующий блокчейн остаётся валидным;
|
||||||
|
- новый функционал появляется только там, где клиент и сервер умеют его понимать.
|
||||||
|
|
||||||
|
### 1.2. Общая стратегия
|
||||||
|
|
||||||
|
Новая модель делится на четыре слоя:
|
||||||
|
|
||||||
|
1. контентные сущности;
|
||||||
|
2. текстовые отзывы и мнения;
|
||||||
|
3. статусные действия пользователей;
|
||||||
|
4. community-события вокруг канала.
|
||||||
|
|
||||||
|
### 1.3. Редактирование и удаление
|
||||||
|
|
||||||
|
Для новых контентных сущностей сохраняется действующий принцип SHiNE:
|
||||||
|
|
||||||
|
- редактирование всегда ссылается на оригинальный блок;
|
||||||
|
- тип сущности edit не меняет;
|
||||||
|
- удаление выполняется через `edit` с пустым текстом;
|
||||||
|
- отдельный `DELETE`-подтип не вводится.
|
||||||
|
|
||||||
|
Это правило особенно важно для:
|
||||||
|
|
||||||
|
- `plain_text`
|
||||||
|
- `exercise`
|
||||||
|
- `service`
|
||||||
|
- `course`
|
||||||
|
- `entrypoint`
|
||||||
|
|
||||||
|
В пользовательских текстах и UI желательно использовать русские названия:
|
||||||
|
|
||||||
|
- обычный текст;
|
||||||
|
- упражнение;
|
||||||
|
- услуга / процедура;
|
||||||
|
- курс;
|
||||||
|
- стартовое сообщение канала.
|
||||||
|
|
||||||
|
## 2. Канонические внутренние ссылки
|
||||||
|
|
||||||
|
В новой модели поддерживаются только две формы внутренней ссылки:
|
||||||
|
|
||||||
|
- каноническая: `SHiNE/<blockchainName>/<blockNumber>`
|
||||||
|
- особополная: `SHiNE/<blockchainName>/<blockNumber>/<blockHash>`
|
||||||
|
|
||||||
|
Примеры:
|
||||||
|
|
||||||
|
- `SHiNE/alice-001/157`
|
||||||
|
- `SHiNE/alice-001/157/abcd1234...`
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
- канонической считается именно короткая форма без хэша;
|
||||||
|
- форма с хэшем используется как усиленный вариант для точной проверки;
|
||||||
|
- внутри UI и серверной логики ссылка должна приводиться как минимум к паре:
|
||||||
|
- `blockchainName`
|
||||||
|
- `blockNumber`
|
||||||
|
- если хэш присутствует, он участвует в дополнительной валидации ссылки.
|
||||||
|
|
||||||
|
## 3. Новые контентные сущности
|
||||||
|
|
||||||
|
## 3.1. Новый `type=5` — `CONTENT`
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
|
||||||
|
- хранение новых смысловых материалов канала;
|
||||||
|
- сохранение линии канала;
|
||||||
|
- поддержка edit-версий и логического удаления.
|
||||||
|
|
||||||
|
### 3.1.1. Подтипы `CONTENT`
|
||||||
|
|
||||||
|
- `subType=10` — `CONTENT_PLAIN`
|
||||||
|
- `subType=11` — `CONTENT_EDIT_PLAIN`
|
||||||
|
- `subType=20` — `CONTENT_EXERCISE`
|
||||||
|
- `subType=21` — `CONTENT_EDIT_EXERCISE`
|
||||||
|
- `subType=30` — `CONTENT_SERVICE`
|
||||||
|
- `subType=31` — `CONTENT_EDIT_SERVICE`
|
||||||
|
- `subType=40` — `CONTENT_COURSE`
|
||||||
|
- `subType=41` — `CONTENT_EDIT_COURSE`
|
||||||
|
- `subType=50` — `CONTENT_ENTRYPOINT`
|
||||||
|
- `subType=51` — `CONTENT_EDIT_ENTRYPOINT`
|
||||||
|
|
||||||
|
### 3.1.2. Семантика подтипов
|
||||||
|
|
||||||
|
- `CONTENT_PLAIN` — обычный текст нового поколения.
|
||||||
|
- `CONTENT_EXERCISE` — упражнение, которое можно выполнять многократно.
|
||||||
|
- `CONTENT_SERVICE` — услуга / процедура, которую можно проходить многократно.
|
||||||
|
- `CONTENT_COURSE` — курс / оглавление.
|
||||||
|
- `CONTENT_ENTRYPOINT` — стартовое сообщение канала.
|
||||||
|
|
||||||
|
### 3.1.3. Почему `entrypoint` отдельный тип
|
||||||
|
|
||||||
|
`entrypoint` не считается курсом.
|
||||||
|
|
||||||
|
Это отдельная сущность, потому что:
|
||||||
|
|
||||||
|
- она описывает вход в канал;
|
||||||
|
- по ней нельзя делать `started / completed / abandoned`;
|
||||||
|
- у канала в каждый момент времени должна быть только одна актуальная стартовая страница.
|
||||||
|
|
||||||
|
### 3.1.4. Ограничение на `entrypoint`
|
||||||
|
|
||||||
|
Для одного канала допускается только один исходный блок `CONTENT_ENTRYPOINT`.
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
- если entrypoint уже существует, создать второй нельзя;
|
||||||
|
- изменять можно только через `CONTENT_EDIT_ENTRYPOINT`;
|
||||||
|
- если entrypoint логически удалён, UI должен считать, что стартовой страницы больше нет;
|
||||||
|
- исторический блок при этом остаётся в цепочке.
|
||||||
|
|
||||||
|
### 3.1.5. Формат body для `CONTENT_*`
|
||||||
|
|
||||||
|
Для `version=1` рекомендуется использовать формат, максимально совместимый по логике с текущими `TEXT_POST` / `TEXT_EDIT_POST`.
|
||||||
|
|
||||||
|
#### Создающие блоки
|
||||||
|
|
||||||
|
Для:
|
||||||
|
|
||||||
|
- `CONTENT_PLAIN`
|
||||||
|
- `CONTENT_EXERCISE`
|
||||||
|
- `CONTENT_SERVICE`
|
||||||
|
- `CONTENT_COURSE`
|
||||||
|
- `CONTENT_ENTRYPOINT`
|
||||||
|
|
||||||
|
body:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ContentLineBody_v1
|
||||||
|
- lineCode: int32
|
||||||
|
- prevLineNumber: int32
|
||||||
|
- prevLineHash32: [32]
|
||||||
|
- thisLineNumber: int32
|
||||||
|
- textLenBytes: uint16
|
||||||
|
- text UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Edit-блоки
|
||||||
|
|
||||||
|
Для:
|
||||||
|
|
||||||
|
- `CONTENT_EDIT_PLAIN`
|
||||||
|
- `CONTENT_EDIT_EXERCISE`
|
||||||
|
- `CONTENT_EDIT_SERVICE`
|
||||||
|
- `CONTENT_EDIT_COURSE`
|
||||||
|
- `CONTENT_EDIT_ENTRYPOINT`
|
||||||
|
|
||||||
|
body:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ContentEditBody_v1
|
||||||
|
- lineCode: int32
|
||||||
|
- prevLineNumber: int32
|
||||||
|
- prevLineHash32: [32]
|
||||||
|
- thisLineNumber: int32
|
||||||
|
- toBlockGlobalNumber: int32
|
||||||
|
- toBlockHash32: [32]
|
||||||
|
- textLenBytes: uint16
|
||||||
|
- text UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
- edit всегда ссылается на оригинальный блок соответствующего типа;
|
||||||
|
- `toBlockchainName` в edit не хранится;
|
||||||
|
- `textLen=0` означает логическое удаление содержимого;
|
||||||
|
- тип исходной сущности edit не меняет.
|
||||||
|
|
||||||
|
### 3.1.6. Что считается комментарием
|
||||||
|
|
||||||
|
Комментарии не требуют нового формата.
|
||||||
|
|
||||||
|
Для обсуждения новых контентных сущностей продолжают использоваться уже существующие:
|
||||||
|
|
||||||
|
- `TEXT_REPLY`
|
||||||
|
- `TEXT_EDIT_REPLY`
|
||||||
|
|
||||||
|
Это позволяет не ломать старую reply-механику и reuse текущую модель тредов.
|
||||||
|
|
||||||
|
## 4. Текстовые отзывы
|
||||||
|
|
||||||
|
## 4.1. Новый `type=6` — `TEXT_RATING`
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
|
||||||
|
- текстовая оценка / отзыв на объект;
|
||||||
|
- без числовой шкалы;
|
||||||
|
- с возможностью редактирования и логического удаления.
|
||||||
|
|
||||||
|
Смысл `TEXT_RATING`:
|
||||||
|
|
||||||
|
- это текст;
|
||||||
|
- это специальный отзыв / мнение / оценка;
|
||||||
|
- это явный сигнал, что перед нами не просто комментарий, а осмысленный отзыв;
|
||||||
|
- в будущем это поле можно отдельно анализировать нейронками.
|
||||||
|
|
||||||
|
### 4.1.1. Подтипы
|
||||||
|
|
||||||
|
- `subType=10` — `TEXT_RATING_POST`
|
||||||
|
- `subType=11` — `TEXT_RATING_EDIT`
|
||||||
|
|
||||||
|
### 4.1.2. Где разрешён `TEXT_RATING_POST`
|
||||||
|
|
||||||
|
Разрешён на target:
|
||||||
|
|
||||||
|
- `HEADER` пользователя;
|
||||||
|
- контентный блок `type=5`;
|
||||||
|
- при необходимости в будущем — на другие target-блоки по отдельному решению.
|
||||||
|
|
||||||
|
Сейчас в данном ТЗ:
|
||||||
|
|
||||||
|
- отзыв / оценка на пользователя — да;
|
||||||
|
- отзыв / оценка на контент — да;
|
||||||
|
- отзыв / лайк на канал целиком — не вводится, только оставляется как будущая возможность.
|
||||||
|
|
||||||
|
### 4.1.3. Где и как используется `TEXT_RATING_POST`
|
||||||
|
|
||||||
|
`TEXT_RATING_POST` можно создавать:
|
||||||
|
|
||||||
|
- как отзыв на контентный блок;
|
||||||
|
- как отзыв на пользователя через target на `HEADER`;
|
||||||
|
- как специальный ответ вместо обычного комментария.
|
||||||
|
|
||||||
|
Практическое правило для UI:
|
||||||
|
|
||||||
|
- при ответе на любое сообщение пользователь может выбрать:
|
||||||
|
- обычный ответ;
|
||||||
|
- `мнение / отзыв`.
|
||||||
|
|
||||||
|
### 4.1.4. Формат body
|
||||||
|
|
||||||
|
#### Создание
|
||||||
|
|
||||||
|
```text
|
||||||
|
TextRatingBody_v1
|
||||||
|
- toBlockchainNameLen: uint8
|
||||||
|
- toBlockchainName UTF-8
|
||||||
|
- toBlockGlobalNumber: int32
|
||||||
|
- toBlockHash32: [32]
|
||||||
|
- textLenBytes: uint16
|
||||||
|
- text UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Редактирование
|
||||||
|
|
||||||
|
```text
|
||||||
|
TextRatingEditBody_v1
|
||||||
|
- toBlockGlobalNumber: int32
|
||||||
|
- toBlockHash32: [32]
|
||||||
|
- textLenBytes: uint16
|
||||||
|
- text UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
- edit ссылается на оригинальный `TEXT_RATING_POST`;
|
||||||
|
- пустой текст в edit означает логическое удаление отзыва.
|
||||||
|
|
||||||
|
## 5. Статусные действия и накопительные события
|
||||||
|
|
||||||
|
## 5.1. Новый `type=7` — `STATUS_ACTION`
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
|
||||||
|
- хранение действий пользователя по отношению к контенту;
|
||||||
|
- вычисление текущего статуса;
|
||||||
|
- накопительный учёт повторных прохождений;
|
||||||
|
- подтверждение статусов другими людьми.
|
||||||
|
|
||||||
|
### 5.1.1. Подтипы
|
||||||
|
|
||||||
|
- `subType=10` — `STATUS_DONE_ONCE`
|
||||||
|
- `subType=20` — `STATUS_INTERESTED`
|
||||||
|
- `subType=30` — `STATUS_STARTED`
|
||||||
|
- `subType=40` — `STATUS_COMPLETED`
|
||||||
|
- `subType=50` — `STATUS_ABANDONED`
|
||||||
|
- `subType=60` — `STATUS_CONFIRMED`
|
||||||
|
|
||||||
|
### 5.1.2. Матрица допустимости по контенту
|
||||||
|
|
||||||
|
`STATUS_DONE_ONCE` разрешён только для:
|
||||||
|
|
||||||
|
- `CONTENT_EXERCISE`
|
||||||
|
- `CONTENT_SERVICE`
|
||||||
|
|
||||||
|
`STATUS_INTERESTED`, `STATUS_STARTED`, `STATUS_COMPLETED`, `STATUS_ABANDONED` разрешены только для:
|
||||||
|
|
||||||
|
- `CONTENT_EXERCISE`
|
||||||
|
- `CONTENT_COURSE`
|
||||||
|
|
||||||
|
`CONTENT_ENTRYPOINT` не поддерживает:
|
||||||
|
|
||||||
|
- `interested`
|
||||||
|
- `started`
|
||||||
|
- `completed`
|
||||||
|
- `abandoned`
|
||||||
|
|
||||||
|
### 5.1.3. Как считать текущее состояние
|
||||||
|
|
||||||
|
Для пары:
|
||||||
|
|
||||||
|
- `actorLogin`
|
||||||
|
- `targetBlock`
|
||||||
|
|
||||||
|
актуальным статусом считается последнее по времени статусное событие из набора:
|
||||||
|
|
||||||
|
- `STATUS_INTERESTED`
|
||||||
|
- `STATUS_STARTED`
|
||||||
|
- `STATUS_COMPLETED`
|
||||||
|
- `STATUS_ABANDONED`
|
||||||
|
|
||||||
|
Следствия:
|
||||||
|
|
||||||
|
- у одного пользователя по одному объекту в каждый момент времени только один актуальный статус;
|
||||||
|
- если последним пришёл `interested`, статус считается “заинтересовался / рассматривает, но ещё не начал”;
|
||||||
|
- если последним пришёл `started`, статус считается “в процессе”;
|
||||||
|
- если последним пришёл `completed`, статус считается “завершён / освоен / знаю”;
|
||||||
|
- если последним пришёл `abandoned`, статус считается “брошен”.
|
||||||
|
|
||||||
|
### 5.1.4. Как считать количество прохождений
|
||||||
|
|
||||||
|
`STATUS_DONE_ONCE` не меняет текущий статус.
|
||||||
|
|
||||||
|
Он считается отдельно как накопительное событие.
|
||||||
|
|
||||||
|
Сервер должен уметь считать:
|
||||||
|
|
||||||
|
- сколько раз пользователь сделал упражнение;
|
||||||
|
- сколько раз пользователь прошёл услугу / процедуру.
|
||||||
|
|
||||||
|
### 5.1.5. Дополнительный текст действия
|
||||||
|
|
||||||
|
Каждое действие `STATUS_*` может содержать дополнительный текст-комментарий.
|
||||||
|
|
||||||
|
Примеры:
|
||||||
|
|
||||||
|
- как именно делал упражнение;
|
||||||
|
- чем заинтересовал курс;
|
||||||
|
- с какими мыслями начал курс;
|
||||||
|
- почему бросил;
|
||||||
|
- что именно подтверждает подтверждающий человек.
|
||||||
|
|
||||||
|
### 5.1.6. Подтверждение статуса
|
||||||
|
|
||||||
|
`STATUS_CONFIRMED` разрешён только на target-статусы:
|
||||||
|
|
||||||
|
- `STATUS_DONE_ONCE`
|
||||||
|
- `STATUS_INTERESTED`
|
||||||
|
- `STATUS_STARTED`
|
||||||
|
- `STATUS_COMPLETED`
|
||||||
|
- `STATUS_ABANDONED`
|
||||||
|
|
||||||
|
Это значит:
|
||||||
|
|
||||||
|
- подтверждение не ставится прямо на курс или упражнение;
|
||||||
|
- подтверждение ставится на конкретный статусный блок другого человека.
|
||||||
|
|
||||||
|
Подтверждение:
|
||||||
|
|
||||||
|
- не меняет основной статус автора;
|
||||||
|
- не меняет счётчик `done_once`;
|
||||||
|
- хранится как отдельное мнение / свидетельство.
|
||||||
|
|
||||||
|
### 5.1.7. Формат body
|
||||||
|
|
||||||
|
Для `STATUS_DONE_ONCE`, `STATUS_INTERESTED`, `STATUS_STARTED`, `STATUS_COMPLETED`, `STATUS_ABANDONED`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
StatusActionBody_v1
|
||||||
|
- toBlockchainNameLen: uint8
|
||||||
|
- toBlockchainName UTF-8
|
||||||
|
- toBlockGlobalNumber: int32
|
||||||
|
- toBlockHash32: [32]
|
||||||
|
- noteLenBytes: uint16
|
||||||
|
- note UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
Для `STATUS_CONFIRMED`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
StatusConfirmBody_v1
|
||||||
|
- toBlockchainNameLen: uint8
|
||||||
|
- toBlockchainName UTF-8
|
||||||
|
- toBlockGlobalNumber: int32
|
||||||
|
- toBlockHash32: [32]
|
||||||
|
- noteLenBytes: uint16
|
||||||
|
- note UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
На уровне бинарного формата тело можно оставить одинаковым.
|
||||||
|
Различие задаётся `subType` и правилами валидации target.
|
||||||
|
|
||||||
|
## 6. Community-события
|
||||||
|
|
||||||
|
## 6.1. Новый `type=8` — `COMMUNITY_EVENT`
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
|
||||||
|
- заявки в сообщество;
|
||||||
|
- выход из сообщества;
|
||||||
|
- принятие;
|
||||||
|
- исключение.
|
||||||
|
|
||||||
|
### 6.1.1. Подтипы
|
||||||
|
|
||||||
|
- `subType=10` — `COMMUNITY_JOIN_REQUEST`
|
||||||
|
- `subType=20` — `COMMUNITY_LEAVE`
|
||||||
|
- `subType=30` — `COMMUNITY_ACCEPT`
|
||||||
|
- `subType=40` — `COMMUNITY_REMOVE`
|
||||||
|
|
||||||
|
### 6.1.2. Базовая логика
|
||||||
|
|
||||||
|
`COMMUNITY_JOIN_REQUEST`
|
||||||
|
|
||||||
|
- создаёт пользователь;
|
||||||
|
- target — `CONTENT_ENTRYPOINT` канала;
|
||||||
|
- может содержать текст заявки.
|
||||||
|
|
||||||
|
`COMMUNITY_LEAVE`
|
||||||
|
|
||||||
|
- создаёт сам участник;
|
||||||
|
- target — `CONTENT_ENTRYPOINT` канала;
|
||||||
|
- подтверждение не требуется;
|
||||||
|
- может содержать текст.
|
||||||
|
|
||||||
|
`COMMUNITY_ACCEPT`
|
||||||
|
|
||||||
|
- создаёт владелец канала;
|
||||||
|
- target — конкретный блок `COMMUNITY_JOIN_REQUEST`;
|
||||||
|
- может содержать текст.
|
||||||
|
|
||||||
|
`COMMUNITY_REMOVE`
|
||||||
|
|
||||||
|
- создаёт владелец канала;
|
||||||
|
- target — `CONTENT_ENTRYPOINT` канала;
|
||||||
|
- body дополнительно хранит `subjectLogin`, кого исключили;
|
||||||
|
- может содержать текст.
|
||||||
|
|
||||||
|
### 6.1.3. Текущее членство
|
||||||
|
|
||||||
|
Пользователь считается текущим участником сообщества, если:
|
||||||
|
|
||||||
|
- у него есть хотя бы одно принятие в это сообщество;
|
||||||
|
- после этого принятия нет более позднего:
|
||||||
|
- `COMMUNITY_LEAVE`
|
||||||
|
- `COMMUNITY_REMOVE`
|
||||||
|
|
||||||
|
Заявка сама по себе членство не создаёт.
|
||||||
|
|
||||||
|
### 6.1.4. Формат body
|
||||||
|
|
||||||
|
Для `JOIN_REQUEST` и `LEAVE`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CommunityActionBody_v1
|
||||||
|
- toBlockchainNameLen: uint8
|
||||||
|
- toBlockchainName UTF-8
|
||||||
|
- toBlockGlobalNumber: int32
|
||||||
|
- toBlockHash32: [32]
|
||||||
|
- noteLenBytes: uint16
|
||||||
|
- note UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
Для `ACCEPT`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CommunityAcceptBody_v1
|
||||||
|
- toBlockchainNameLen: uint8
|
||||||
|
- toBlockchainName UTF-8
|
||||||
|
- toBlockGlobalNumber: int32
|
||||||
|
- toBlockHash32: [32]
|
||||||
|
- noteLenBytes: uint16
|
||||||
|
- note UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
Для `REMOVE`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CommunityRemoveBody_v1
|
||||||
|
- toBlockchainNameLen: uint8
|
||||||
|
- toBlockchainName UTF-8
|
||||||
|
- toBlockGlobalNumber: int32
|
||||||
|
- toBlockHash32: [32]
|
||||||
|
- subjectLoginLen: uint8
|
||||||
|
- subjectLogin ASCII
|
||||||
|
- noteLenBytes: uint16
|
||||||
|
- note UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Что остаётся на старых типах
|
||||||
|
|
||||||
|
### 7.0. Обычный канал и лента достижений
|
||||||
|
|
||||||
|
На уровне продукта рекомендуется различать:
|
||||||
|
|
||||||
|
- обычный канал постов пользователя;
|
||||||
|
- отдельную ленту его действий и достижений.
|
||||||
|
|
||||||
|
В обычном канале пользователь:
|
||||||
|
|
||||||
|
- пишет посты;
|
||||||
|
- публикует материалы;
|
||||||
|
- общается и обсуждает.
|
||||||
|
|
||||||
|
В ленте достижений видны события:
|
||||||
|
|
||||||
|
- какие упражнения он делал;
|
||||||
|
- какие услуги / процедуры проходил;
|
||||||
|
- какие курсы его заинтересовали;
|
||||||
|
- какие курсы он начал;
|
||||||
|
- какие курсы он завершил;
|
||||||
|
- что он бросил.
|
||||||
|
|
||||||
|
В данном ТЗ эта модель фиксируется как продуктовая логика.
|
||||||
|
Конкретный способ хранения можно реализовать:
|
||||||
|
|
||||||
|
- либо отдельным специальным каналом;
|
||||||
|
- либо отдельным режимом чтения по статусным блокам.
|
||||||
|
|
||||||
|
### 7.1. Лайк пользователю
|
||||||
|
|
||||||
|
Лайк пользователю не вводится как `REACTION`.
|
||||||
|
|
||||||
|
Он остаётся в слое социальных связей:
|
||||||
|
|
||||||
|
- через `CONNECTION`
|
||||||
|
- как будущий отдельный подтип связи
|
||||||
|
|
||||||
|
В этом ТЗ сам новый подтип связи не описывается детально.
|
||||||
|
Нужно только зафиксировать правило:
|
||||||
|
|
||||||
|
- лайк человека относится к графу связей, а не к реакции на блок.
|
||||||
|
|
||||||
|
### 7.2. Лайк контента
|
||||||
|
|
||||||
|
Лайк на:
|
||||||
|
|
||||||
|
- `CONTENT_PLAIN`
|
||||||
|
- `CONTENT_EXERCISE`
|
||||||
|
- `CONTENT_SERVICE`
|
||||||
|
- `CONTENT_COURSE`
|
||||||
|
- `CONTENT_ENTRYPOINT`
|
||||||
|
|
||||||
|
может использовать уже существующий:
|
||||||
|
|
||||||
|
- `REACTION_LIKE`
|
||||||
|
- `REACTION_UNLIKE`
|
||||||
|
|
||||||
|
Отдельный новый формат для лайка контента не нужен.
|
||||||
|
|
||||||
|
### 7.3. Канал целиком
|
||||||
|
|
||||||
|
В текущем ТЗ не вводятся:
|
||||||
|
|
||||||
|
- отзыв на канал целиком;
|
||||||
|
- лайк канала целиком.
|
||||||
|
|
||||||
|
Это оставляется как будущая возможность.
|
||||||
|
|
||||||
|
### 7.4. Отзывы о людях
|
||||||
|
|
||||||
|
Отзывы о человеке как о человеке в текущем ТЗ допустимы через `TEXT_RATING` на `HEADER`.
|
||||||
|
|
||||||
|
Но продуктовую модель их показа нужно отдельно продумать.
|
||||||
|
|
||||||
|
Направление для будущего:
|
||||||
|
|
||||||
|
- просмотр отзывов о человеке на вкладке связей;
|
||||||
|
- приоритетный вывод отзывов от близких друзей, родственников, друзей и контактов;
|
||||||
|
- затем вывод остальных отзывов.
|
||||||
|
|
||||||
|
Эта тема полезна, но требует дополнительной осторожной проработки с точки зрения UX и социальных рисков.
|
||||||
|
|
||||||
|
## 8. Требования к серверу
|
||||||
|
|
||||||
|
Сервер после внедрения должен уметь:
|
||||||
|
|
||||||
|
1. Валидировать новые `type=5..8`.
|
||||||
|
2. Хранить новые блоки без ломки старого чтения.
|
||||||
|
3. Определять текущий статус пользователя по объекту:
|
||||||
|
- `interested`
|
||||||
|
- `started`
|
||||||
|
- `completed`
|
||||||
|
- `abandoned`
|
||||||
|
4. Считать накопительные события `done_once` для:
|
||||||
|
- `exercise`
|
||||||
|
- `service`
|
||||||
|
5. Считать подтверждения статусов.
|
||||||
|
6. Определять единственный актуальный `entrypoint` канала.
|
||||||
|
7. Определять текущее членство в сообществе канала.
|
||||||
|
8. Поддерживать внутренние ссылки вида:
|
||||||
|
- `SHiNE/<blockchainName>/<blockNumber>`
|
||||||
|
- `SHiNE/<blockchainName>/<blockNumber>/<blockHash>`
|
||||||
|
|
||||||
|
## 9. Требования к UI
|
||||||
|
|
||||||
|
UI после внедрения должен уметь:
|
||||||
|
|
||||||
|
1. Показывать разные карточки для:
|
||||||
|
- текста
|
||||||
|
- упражнения
|
||||||
|
- услуги
|
||||||
|
- курса
|
||||||
|
- entrypoint
|
||||||
|
2. Показывать стартовую страницу канала, если `entrypoint` существует.
|
||||||
|
3. Не показывать entrypoint, если он логически удалён.
|
||||||
|
4. Давать человеку только допустимые действия по типу материала.
|
||||||
|
5. Показывать:
|
||||||
|
- текущий статус;
|
||||||
|
- количество `done_once`;
|
||||||
|
- подтверждения статуса.
|
||||||
|
6. Показывать отдельные действия-кнопки на специальных блоках, например:
|
||||||
|
- `Выполнил упражнение`
|
||||||
|
- `Прошёл процедуру`
|
||||||
|
- `Заинтересовало`
|
||||||
|
- `Начал курс`
|
||||||
|
- `Закончил курс`
|
||||||
|
7. При ответе на сообщение давать выбор:
|
||||||
|
- обычный ответ;
|
||||||
|
- `мнение / отзыв`.
|
||||||
|
8. Открывать внутренние ссылки SHiNE.
|
||||||
|
|
||||||
|
## 10. Вывод по совместимости
|
||||||
|
|
||||||
|
Предлагаемая модель реализуема без слома старого блокчейна.
|
||||||
|
|
||||||
|
Причина:
|
||||||
|
|
||||||
|
- старые `type=0..4` не меняются;
|
||||||
|
- новые сущности вводятся только как новые `type=5..8`;
|
||||||
|
- существующие `reply`, `like`, `edit`, `HEADER`, `CREATE_CHANNEL` и `CONNECTION` продолжают работать как раньше;
|
||||||
|
- старые клиенты смогут игнорировать новые типы как неизвестные;
|
||||||
|
- новые клиенты смогут постепенно включать поддержку нового функционала.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
|
||||||
|
- это расширение формата блокчейна;
|
||||||
|
- это не миграция со сломом старых блоков;
|
||||||
|
- это можно внедрять поэтапно.
|
||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.327
|
client.version=1.2.330
|
||||||
server.version=1.2.298
|
server.version=1.2.302
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
проверь ещё id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
def appVersionProps = new Properties()
|
def appVersionProps = new Properties()
|
||||||
@@ -216,7 +216,7 @@ tasks.register('deployServer', Exec) {
|
|||||||
dependsOn shadowJar
|
dependsOn shadowJar
|
||||||
workingDir = rootDir
|
workingDir = rootDir
|
||||||
environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath
|
environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath
|
||||||
commandLine 'bash', file('deploy_shine-server_test2.sh').absolutePath
|
commandLine 'bash', file('deploy_shine-server_server2shineupme.sh').absolutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('deployUI', Exec) {
|
tasks.register('deployUI', Exec) {
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
|
|
||||||
- `shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java`.
|
- `shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java`.
|
||||||
|
|
||||||
Если операция зарегистрирована в `HANDLERS` и `REQUEST_TYPES`, она считается доступной через JSON/WebSocket API. Общий актуальный индекс таких операций поддерживается в `Dev_Docs/API/09_Operations_Index.md`.
|
Если операция зарегистрирована в `HANDLERS` и `REQUEST_TYPES`, она считается доступной через JSON/WebSocket API. Общий актуальный индекс таких операций поддерживается в `docs/API/09_Operations_Index.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
Отдельно появился новый серверный сценарий pairing через доверенный homeserver/ESP. Он не заменяет обычный вход и описан в:
|
Отдельно появился новый серверный сценарий pairing через доверенный homeserver/ESP. Он не заменяет обычный вход и описан в:
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md`
|
- `docs/Протоколы/ESP_Pairing_и_режимы_подключения.md`
|
||||||
|
|
||||||
Кратко:
|
Кратко:
|
||||||
|
|
||||||
@@ -321,7 +321,7 @@ SESSION_LOGIN:{sessionId}:{timeMs}:{nonce}
|
|||||||
|
|
||||||
Точные форматы этих операций см. в `03_Session_Management_API.md` и в протокольном документе:
|
Точные форматы этих операций см. в `03_Session_Management_API.md` и в протокольном документе:
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md`
|
- `docs/Протоколы/ESP_Pairing_и_режимы_подключения.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
Подробная логика DM и бинарного формата:
|
Подробная логика DM и бинарного формата:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
- `docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`
|
- `docs/Personal_Messages/Формат_DM_v1.md`
|
||||||
|
|
||||||
Важно:
|
Важно:
|
||||||
|
|
||||||
|
|||||||
@@ -22,4 +22,4 @@
|
|||||||
|
|
||||||
## Примечание
|
## Примечание
|
||||||
|
|
||||||
Если нужно добавить новый тип или подтип блока, сначала обновляйте профильный файл этого раздела, затем API-документацию в `Dev_Docs/API`.
|
Если нужно добавить новый тип или подтип блока, сначала обновляйте профильный файл этого раздела, затем API-документацию в `docs/API`.
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
- после рестарта сервер добивает `BlockchainTmpRecovery` и `BlockchainResyncRecovery`;
|
- после рестарта сервер добивает `BlockchainTmpRecovery` и `BlockchainResyncRecovery`;
|
||||||
- `aidartest-001` успешно подтягивается с `shineup.me`;
|
- `aidartest-001` успешно подтягивается с `shineup.me`;
|
||||||
- итоговое локальное состояние по `aidartest-001` дошло до `last_block_number=13`.
|
- итоговое локальное состояние по `aidartest-001` дошло до `last_block_number=13`.
|
||||||
- В `Dev_Docs/Blockchain/sync-between-servers.md` добавлен практический результат ручной проверки на тестовом сервере.
|
- В `docs/Blockchain/sync-between-servers.md` добавлен практический результат ручной проверки на тестовом сервере.
|
||||||
|
|
||||||
## 2026-06-26 17:03:22 +0400
|
## 2026-06-26 17:03:22 +0400
|
||||||
- Базовый коммит-ориентир: `71fdee0`.
|
- Базовый коммит-ориентир: `71fdee0`.
|
||||||
@@ -60,21 +60,21 @@
|
|||||||
- `BlockchainTmpRecoveryOnStartup` теперь разбирает marker-driven recovery для обычной записи блока:
|
- `BlockchainTmpRecoveryOnStartup` теперь разбирает marker-driven recovery для обычной записи блока:
|
||||||
- если marker есть, recovery либо завершает swap tmp -> main, либо удаляет мусор;
|
- если marker есть, recovery либо завершает swap tmp -> main, либо удаляет мусор;
|
||||||
- если marker нет, временные артефакты считаются мусором и удаляются.
|
- если marker нет, временные артефакты считаются мусором и удаляются.
|
||||||
- В `Dev_Docs/Blockchain/sync-between-servers.md` добавлено описание обычного `AddBlock` recovery и разделение между `write_pending` и `resync_pending`.
|
- В `docs/Blockchain/sync-between-servers.md` добавлено описание обычного `AddBlock` recovery и разделение между `write_pending` и `resync_pending`.
|
||||||
|
|
||||||
## 2026-05-24 11:40:00 +0300
|
## 2026-05-24 11:40:00 +0300
|
||||||
- Базовый коммит-ориентир: `abdce05`.
|
- Базовый коммит-ориентир: `abdce05`.
|
||||||
- `TEXT_REPOST (subType=30)` оставлен как зарезервированный формат, но новые блоки репоста временно отключены на уровне `AddBlock`.
|
- `TEXT_REPOST (subType=30)` оставлен как зарезервированный формат, но новые блоки репоста временно отключены на уровне `AddBlock`.
|
||||||
- В `11_TEXT_Blocks.md` зафиксировано, что запись `TEXT_REPOST` временно не используется до будущей реализации.
|
- В `11_TEXT_Blocks.md` зафиксировано, что запись `TEXT_REPOST` временно не используется до будущей реализации.
|
||||||
- В `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md` добавлен код отказа `repost_disabled`.
|
- В `docs/API/04_Add_Block_to_Blockchain_API.md` добавлен код отказа `repost_disabled`.
|
||||||
|
|
||||||
## 2026-05-21 19:05:00 +0300
|
## 2026-05-21 19:05:00 +0300
|
||||||
- Базовый коммит-ориентир: `5344c42`.
|
- Базовый коммит-ориентир: `5344c42`.
|
||||||
- Добавлен новый TEXT-подтип `TEXT_REPOST (subType=30)`:
|
- Добавлен новый TEXT-подтип `TEXT_REPOST (subType=30)`:
|
||||||
- обновлён перечень типов в `11_TEXT_Blocks.md`;
|
- обновлён перечень типов в `11_TEXT_Blocks.md`;
|
||||||
- обновлена быстрая карта типов в `00_Blockchain_Formats_and_Block_Types.md`.
|
- обновлена быстрая карта типов в `00_Blockchain_Formats_and_Block_Types.md`.
|
||||||
- Уточнено API-описание поддержанных подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
|
- Уточнено API-описание поддержанных подтипов в `docs/API/04_Add_Block_to_Blockchain_API.md`.
|
||||||
- В документе `Dev_Docs/API/08_MCP_Чтение_и_дозапись_персонального_публичного_чата.md` зафиксировано, что чтение канала учитывает `TEXT_POST` и `TEXT_REPOST`.
|
- В документе `docs/API/08_MCP_Чтение_и_дозапись_персонального_публичного_чата.md` зафиксировано, что чтение канала учитывает `TEXT_POST` и `TEXT_REPOST`.
|
||||||
|
|
||||||
## 2026-05-20 11:34:17 +0300
|
## 2026-05-20 11:34:17 +0300
|
||||||
- Базовый коммит-ориентир: `a53444b`.
|
- Базовый коммит-ориентир: `a53444b`.
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
- `60/61` — `known_person / unknown_person` (знаю этого человека);
|
- `60/61` — `known_person / unknown_person` (знаю этого человека);
|
||||||
- `70/71` — `shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий);
|
- `70/71` — `shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий);
|
||||||
- `74/75` — `shine_seen / shine_unseen` (мало знаком, но видел сияющим).
|
- `74/75` — `shine_seen / shine_unseen` (мало знаком, но видел сияющим).
|
||||||
- Обновлён список CONNECTION-подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
|
- Обновлён список CONNECTION-подтипов в `docs/API/04_Add_Block_to_Blockchain_API.md`.
|
||||||
|
|
||||||
## 2026-05-19 20:30:21 +0300
|
## 2026-05-19 20:30:21 +0300
|
||||||
- Базовый коммит-ориентир: `7986184`.
|
- Базовый коммит-ориентир: `7986184`.
|
||||||
@@ -107,4 +107,4 @@
|
|||||||
- Для персонального канала (`type=100`) включена сборка парного потока при чтении (`A->B` + `B->A`, если существует).
|
- Для персонального канала (`type=100`) включена сборка парного потока при чтении (`A->B` + `B->A`, если существует).
|
||||||
- Добавлена поддержка командного префикса `/.` и команды `/.desc` для актуализации описания канала при чтении.
|
- Добавлена поддержка командного префикса `/.` и команды `/.desc` для актуализации описания канала при чтении.
|
||||||
- Зафиксированы команды `/.add` и `/.remove` для каналов `type=200` (зарезервировано под расширение участниками).
|
- Зафиксированы команды `/.add` и `/.remove` для каналов `type=200` (зарезервировано под расширение участниками).
|
||||||
- В `AGENTS.md` добавлено обязательное правило актуализации документации в `Dev_Docs/Blockchain/`.
|
- В `AGENTS.md` добавлено обязательное правило актуализации документации в `docs/Blockchain/`.
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ Full resync запускается только тогда, когда:
|
|||||||
### 5.4 Разрешение конфликтов
|
### 5.4 Разрешение конфликтов
|
||||||
|
|
||||||
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
|
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
|
||||||
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `Dev_Docs/Blockchain/README.md`).
|
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `docs/Blockchain/README.md`).
|
||||||
- DM: конфликтов нет, `message_key` уникален.
|
- DM: конфликтов нет, `message_key` уникален.
|
||||||
|
|
||||||
## 6. Маршрутизация DM между серверами
|
## 6. Маршрутизация DM между серверами
|
||||||
|
|||||||
@@ -197,7 +197,7 @@
|
|||||||
- нужен реальный прогон на test2;
|
- нужен реальный прогон на test2;
|
||||||
- затронута интеграция с Solana;
|
- затронута интеграция с Solana;
|
||||||
|
|
||||||
тогда нужно добавить файл в `Dev_Docs/Pending_Features/`.
|
тогда нужно добавить файл в `docs/Pending_Features/`.
|
||||||
|
|
||||||
## Что пока не оформлено для Miro
|
## Что пока не оформлено для Miro
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
> Если в коде меняется деривация (формула секрета, параметры Argon2id, соль, формула
|
> Если в коде меняется деривация (формула секрета, параметры Argon2id, соль, формула
|
||||||
> ключа, разделитель `|`, набор/имена суффиксов, формат homeserver-ключа, связь
|
> ключа, разделитель `|`, набор/имена суффиксов, формат homeserver-ключа, связь
|
||||||
> dev-ключ ↔ Solana-адрес) — **в том же изменении обязательно править этот документ**.
|
> dev-ключ ↔ Solana-адрес) — **в том же изменении обязательно править этот документ**.
|
||||||
> Роли и назначение ключей описаны отдельно в `Dev_Docs/Keys/README.md` (архитектура).
|
> Роли и назначение ключей описаны отдельно в `docs/Keys/README.md` (архитектура).
|
||||||
> Здесь — только механика. Документ намеренно краткий.
|
> Здесь — только механика. Документ намеренно краткий.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -59,7 +59,7 @@ seed(32) = SHA-256(material)
|
|||||||
| device / **Solana** | `client.key` | Ключ устройства = Solana-ключ. Fee payer и подпись Solana-транзакций; адрес кошелька = `base58(clientPub)`. См. §3. |
|
| device / **Solana** | `client.key` | Ключ устройства = Solana-ключ. Fee payer и подпись Solana-транзакций; адрес кошелька = `base58(clientPub)`. См. §3. |
|
||||||
| homeserver | `homeserver.key:<имя>` | Ключ homeserver-устройства, по одному на каждый homeserver (различитель — имя). См. §4. |
|
| homeserver | `homeserver.key:<имя>` | Ключ homeserver-устройства, по одному на каждый homeserver (различитель — имя). См. §4. |
|
||||||
|
|
||||||
Полные роли каждого ключа — в `Dev_Docs/Keys/README.md`.
|
Полные роли каждого ключа — в `docs/Keys/README.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ seed(32) = SHA-256(material)
|
|||||||
|
|
||||||
Кратко про роли на Solana: `root.key` — это **главный (master) ключ**: им управляют PDA-записью
|
Кратко про роли на Solana: `root.key` — это **главный (master) ключ**: им управляют PDA-записью
|
||||||
(`create/update`) и через это можно заменить все остальные ключи; `client.key` — это **пополняемый
|
(`create/update`) и через это можно заменить все остальные ключи; `client.key` — это **пополняемый
|
||||||
кошелёк и плательщик комиссий**. Полное описание ролей — `Dev_Docs/Keys/README.md`.
|
кошелёк и плательщик комиссий**. Полное описание ролей — `docs/Keys/README.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
`root key` — это **главный (master) ключ** в следующем смысле: зная `root key`, можно управлять пользовательской PDA-записью в Solana (`create_user_pda` / `update_user_pda`) и тем самым **заменить все остальные ключи** пользователя (device, blockchain, homeserver). Поэтому компрометация `root key` равносильна компрометации всей личности пользователя.
|
`root key` — это **главный (master) ключ** в следующем смысле: зная `root key`, можно управлять пользовательской PDA-записью в Solana (`create_user_pda` / `update_user_pda`) и тем самым **заменить все остальные ключи** пользователя (device, blockchain, homeserver). Поэтому компрометация `root key` равносильна компрометации всей личности пользователя.
|
||||||
|
|
||||||
Важно не путать авторитет и кошелёк: `root key` — это авторитет над PDA-записью, а **SOL-комиссии за create/update платит `client key`** (он же fee payer и адрес для пополнения). Подробнее о том, какой ключ за что отвечает на Solana, — в `Dev_Docs/Keys/DERIVATION.md`, §3.
|
Важно не путать авторитет и кошелёк: `root key` — это авторитет над PDA-записью, а **SOL-комиссии за create/update платит `client key`** (он же fee payer и адрес для пополнения). Подробнее о том, какой ключ за что отвечает на Solana, — в `docs/Keys/DERIVATION.md`, §3.
|
||||||
|
|
||||||
## `blockchain key`
|
## `blockchain key`
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
Arweave-кошелёк должен выводиться из `client key` по протоколу:
|
Arweave-кошелёк должен выводиться из `client key` по протоколу:
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md`
|
- `docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md`
|
||||||
|
|
||||||
Если пользователь теряет только `client key`, в худшем случае ломается повседневная переписка и доступ конкретных устройств к ежедневным операциям. `root key` и `blockchain key` при правильной архитектуре остаются отдельно защищёнными.
|
Если пользователь теряет только `client key`, в худшем случае ломается повседневная переписка и доступ конкретных устройств к ежедневным операциям. `root key` и `blockchain key` при правильной архитектуре остаются отдельно защищёнными.
|
||||||
|
|
||||||
@@ -158,13 +158,13 @@ Self-message - это сообщение пользователя самому
|
|||||||
|
|
||||||
## Связанные документы
|
## Связанные документы
|
||||||
|
|
||||||
- `Dev_Docs/Keys/DERIVATION.md` - **источник истины по конкретной деривации** секрета и ключей (формулы Argon2id, `base64|suffix→SHA-256→Ed25519`, суффиксы `root.key`/`bch.key`/`client.key`/`homeserver.key:<имя>`, Solana-ключ, ссылки на код).
|
- `docs/Keys/DERIVATION.md` - **источник истины по конкретной деривации** секрета и ключей (формулы Argon2id, `base64|suffix→SHA-256→Ed25519`, суффиксы `root.key`/`bch.key`/`client.key`/`homeserver.key:<имя>`, Solana-ключ, ссылки на код).
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` - текущая логическая документация личных сообщений.
|
- `docs/Personal_Messages/Протокол_DM_v1.md` - текущая логическая документация личных сообщений.
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md` - точный байтовый формат личных сообщений.
|
- `docs/Personal_Messages/Формат_DM_v1.md` - точный байтовый формат личных сообщений.
|
||||||
- `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
- `docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
||||||
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
- `docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
||||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md` - деплой и первичная инициализация Solana-регистрации.
|
- `docs/Инициализация_Solana_регистрации/README.md` - деплой и первичная инициализация Solana-регистрации.
|
||||||
- `Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md` - derivation Arweave-кошелька из `client key`.
|
- `docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md` - derivation Arweave-кошелька из `client key`.
|
||||||
|
|
||||||
## Что нужно уточнить перед реализацией
|
## Что нужно уточнить перед реализацией
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Экран проверки public Solana RPC в UI
|
||||||
|
|
||||||
|
- статус: `pending`
|
||||||
|
|
||||||
|
## Кратко
|
||||||
|
|
||||||
|
Добавлен отдельный экран разработчика для проверки публичных mainnet Solana RPC прямо из браузера.
|
||||||
|
Экран отправляет реальный JSON-RPC `getVersion` запрос к списку public endpoint-ов и показывает,
|
||||||
|
какие ноды реально подходят для browser use.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
- в `Настройки разработчика` появилась кнопка `Solana: проверить public RPC`;
|
||||||
|
- экран открывается без ошибок;
|
||||||
|
- по каждой mainnet ноде показывается итоговый статус;
|
||||||
|
- для доступных нод видно HTTP-статус, время ответа и версию `solana-core`;
|
||||||
|
- длинные URL и ошибки не распирают экран по ширине на телефоне.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- можно визуально увидеть, какие public Solana RPC доступны именно из браузера;
|
||||||
|
- проверка работает без промежуточного сервера;
|
||||||
|
- экран остаётся mobile-first и не требует горизонтального скролла.
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
## Кратко
|
||||||
|
- Переделан финальный flow регистрации: сначала экран ожидания подтверждения Solana с прогрессом и повторными проверками, потом отдельный экран успешной регистрации с кнопкой входа в стандартный экран сохранения ключей.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
- После отправки регистрации открывается экран ожидания с текстом про подтверждение Solana и кликабельным `Tx ID`.
|
||||||
|
- Прогресс-бар сначала идёт быстрее, потом заметно замедляется.
|
||||||
|
- Первая проверка регистрации начинается примерно через 4 секунды, далее повторяется раз в 2 секунды.
|
||||||
|
- Пока подтверждения нет, снизу показываются понятные статусы ожидания.
|
||||||
|
- После подтверждения открывается экран «Поздравляем с регистрацией» с одной кнопкой `Войти в аккаунт`.
|
||||||
|
- Кнопка `Войти в аккаунт` открывает штатный экран сохранения ключей с тремя галочками.
|
||||||
|
- Стрелка назад в левом верхнем углу на обоих экранах возвращает в главное меню.
|
||||||
|
- После успешного сохранения ключей регистрационный черновик и адрес кошелька очищаются.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- Пользователь не видит преждевременное поздравление до подтверждения регистрации в Solana.
|
||||||
|
- Финальный успех показывается только после появления подтверждения регистрации.
|
||||||
|
- Вход после регистрации идёт через тот же стандартный сценарий сохранения ключей, что и в обычном login-flow.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
- pending
|
||||||
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
Точка входа:
|
Точка входа:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` — логика протокола, роли API, серверное поведение, routing по `access_servers`
|
- `docs/Personal_Messages/Протокол_DM_v1.md` — логика протокола, роли API, серверное поведение, routing по `access_servers`
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md` — точный бинарный формат контейнера `SHiNE_DM`
|
- `docs/Personal_Messages/Формат_DM_v1.md` — точный бинарный формат контейнера `SHiNE_DM`
|
||||||
- `Dev_Docs/Personal_Messages/Технические_вставки_DM_v1.md` — формат специальных `<SHiNE:...>` вставок внутри plaintext DM после расшифровки
|
- `docs/Personal_Messages/Технические_вставки_DM_v1.md` — формат специальных `<SHiNE:...>` вставок внутри plaintext DM после расшифровки
|
||||||
|
|
||||||
Исторический устаревший документ сохранён отдельно:
|
Исторический устаревший документ сохранён отдельно:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md`
|
- `docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md`
|
||||||
|
|
||||||
Правило сопровождения:
|
Правило сопровождения:
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,15 @@
|
|||||||
|
|
||||||
Точный байтовый формат контейнера вынесен отдельно:
|
Точный байтовый формат контейнера вынесен отдельно:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`
|
- `docs/Personal_Messages/Формат_DM_v1.md`
|
||||||
|
|
||||||
Формат клиентских технических вставок внутри plaintext вынесен отдельно:
|
Формат клиентских технических вставок внутри plaintext вынесен отдельно:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Технические_вставки_DM_v1.md`
|
- `docs/Personal_Messages/Технические_вставки_DM_v1.md`
|
||||||
|
|
||||||
Устаревшая предыдущая версия сохранена отдельно:
|
Устаревшая предыдущая версия сохранена отдельно:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md`
|
- `docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md`
|
||||||
|
|
||||||
## 1. Основная модель
|
## 1. Основная модель
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
Подробности стандартного преобразования:
|
Подробности стандартного преобразования:
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/Преобразование_ED25519_в_X25519.md`
|
- `docs/Протоколы/Преобразование_ED25519_в_X25519.md`
|
||||||
|
|
||||||
### 2.2. Правило шифрования копий
|
### 2.2. Правило шифрования копий
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Aктуальная целевая спецификация:
|
Aктуальная целевая спецификация:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
- `docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
|
|
||||||
Что в этом документе считать устаревшим:
|
Что в этом документе считать устаревшим:
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ Aктуальная целевая спецификация:
|
|||||||
|
|
||||||
Черновик будущих вложений вынесен отдельно:
|
Черновик будущих вложений вынесен отдельно:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Черновик_будущих_DM_вложений.md`
|
- `docs/Personal_Messages/Черновик_будущих_DM_вложений.md`
|
||||||
|
|
||||||
## Общая схема
|
## Общая схема
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
Логика протокола, API и поведение сервера описаны отдельно:
|
Логика протокола, API и поведение сервера описаны отдельно:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
- `docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
|
|
||||||
## 1. Общие правила
|
## 1. Общие правила
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Документ описывает целевой формат пользовательской PDA-записи `user_pda` для Solana-программы `shine_users`.
|
Документ описывает целевой формат пользовательской PDA-записи `user_pda` для Solana-программы `shine_users`.
|
||||||
|
|
||||||
Это не формат основного блокчейна SHiNE и не документация по `AddBlock`. Основной блокчейн SHiNE описан отдельно в `Dev_Docs/Blockchain/`.
|
Это не формат основного блокчейна SHiNE и не документация по `AddBlock`. Основной блокчейн SHiNE описан отдельно в `docs/Blockchain/`.
|
||||||
|
|
||||||
Статус документа: итоговый согласованный формат, к которому приведены `create_user_pda`, `update_user_pda` и тестовый сериализатор Solana-модуля.
|
Статус документа: итоговый согласованный формат, к которому приведены `create_user_pda`, `update_user_pda` и тестовый сериализатор Solana-модуля.
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
Связанные документы:
|
Связанные документы:
|
||||||
|
|
||||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md` — single source of truth по деплою и первичной инициализации регистрации пользователей.
|
- `docs/Инициализация_Solana_регистрации/README.md` — single source of truth по деплою и первичной инициализации регистрации пользователей.
|
||||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` — точный формат `user_pda` для `shine_users`.
|
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` — точный формат `user_pda` для `shine_users`.
|
||||||
- `shine-solana/shine/doc/FUNDS_FLOW.md` — короткая справка по денежным потокам внутри Solana-модуля.
|
- `shine-solana/shine/doc/FUNDS_FLOW.md` — короткая справка по денежным потокам внутри Solana-модуля.
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ DAO в текущем виде не является отдельной Anchor-
|
|||||||
1. `shine-solana/shine/Anchor.toml`
|
1. `shine-solana/shine/Anchor.toml`
|
||||||
2. `declare_id!` в `programs/*/src/lib.rs`
|
2. `declare_id!` в `programs/*/src/lib.rs`
|
||||||
3. `programs/common/src/deploy_config.rs`
|
3. `programs/common/src/deploy_config.rs`
|
||||||
4. UI/серверные константы, перечисленные в `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
4. UI/серверные константы, перечисленные в `docs/Инициализация_Solana_регистрации/README.md`
|
||||||
|
|
||||||
## Ключи и authority
|
## Ключи и authority
|
||||||
|
|
||||||
|
|||||||
@@ -115,4 +115,4 @@ sha256$<hex( SHA-256("shine-pairing|" + lower(login.trim()) + "|" + password) )>
|
|||||||
|
|
||||||
Для отдельного RPC-взаимодействия между браузерным wallet-расширением и ESP32 см. документ:
|
Для отдельного RPC-взаимодействия между браузерным wallet-расширением и ESP32 см. документ:
|
||||||
|
|
||||||
- [Формат_взаимодействия_внешнего_кошелька_и_ESP32.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Dev_Docs/Протоколы/Формат_взаимодействия_внешнего_кошелька_и_ESP32.md)
|
- [Формат_взаимодействия_внешнего_кошелька_и_ESP32.md](/home/ai/work/SHiNE/SHiNE-server-sha256/docs/Протоколы/Формат_взаимодействия_внешнего_кошелька_и_ESP32.md)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,183 +0,0 @@
|
|||||||
# Интерактивная карта связей (force-directed graph)
|
|
||||||
|
|
||||||
Экран **«Связи»** (`network-view`) — интерактивная нод-граф карта вместо статичного списка:
|
|
||||||
фокусный пользователь в центре, связи на орбите, навигация тапом/свайпом, премиальные
|
|
||||||
переходы в духе нативного iOS.
|
|
||||||
|
|
||||||
## Где код
|
|
||||||
- `js/pages/network/force-graph.js` — **движок** (физика, рендер, жизненный цикл узлов, жесты).
|
|
||||||
- `js/pages/network/adapter.js` — реальные данные → нейтральная модель движка.
|
|
||||||
- `js/pages/network/node-menu.js` — общее контекстное меню узла.
|
|
||||||
- `js/pages/network/lab.js` — лаборатория (`network-view/lab`) на мок-данных, без бэкенда.
|
|
||||||
- `js/pages/network-view.js` — страница: шапка, поиск, фильтры, история, склейка с движком.
|
|
||||||
- `js/mock-data.js` — `networkGraphUsers` (связанный мульти-граф из 10 человек для лаборатории).
|
|
||||||
- `styles/network-graph.css` — все стили `.fg-*`.
|
|
||||||
|
|
||||||
## Данные (read-only, сервер не трогаем)
|
|
||||||
Единый источник — `authService.getUserConnectionsGraph(login)` (один запрос: логин → прямые связи).
|
|
||||||
`network-view.js` → `buildGraphModel()` нормализует роли (parent/child/sibling/spouse/friend/contact),
|
|
||||||
направление и метки; `adapter.engineModelFromGraphModel()` превращает это в модель движка:
|
|
||||||
`{ focusId, nodes:[{ id, login, name, avatar, relationType, strength, shining, tier }] }`.
|
|
||||||
|
|
||||||
## Модель движка и API
|
|
||||||
`createForceGraph({ stage, model, onNodeTap, onCenterTap, onNodeLongPress })` →
|
|
||||||
`{ setModel(model), setFilter(pred), recenter(id), getFocusNode(), destroy() }`.
|
|
||||||
|
|
||||||
## Ключевые механики
|
|
||||||
- **Diffing-переходы (непрерывность состояний):** при смене фокуса общие узлы (тот же `id`) не
|
|
||||||
пересоздаются, а перелетают на новые места; новые «расцветают» (bloom) каскадом из центра;
|
|
||||||
исчезнувшие уходят в Ghost-слой.
|
|
||||||
- **CSS-bloom (разлёт без тряски):** разлёт/перелёт узлов делают нативные CSS-переходы на
|
|
||||||
`transform` (компоновщик, `cubic-bezier(0.16,1,0.3,1)`, `BLOOM_MS` со ступенчатой задержкой
|
|
||||||
`order × 40мс`), а НЕ JS-физика. Работает даже при троттлинге rAF; цикл лишь ведёт лучи за узлами
|
|
||||||
(`syncPositionsFromDOM`). Завершение — гарантированно по таймеру (`endCssBloom`).
|
|
||||||
- **Ghost-слой:** снимок только **аватарок** старого графа (без линий — иначе старые связи висят
|
|
||||||
«ошмётками»). Полноэкранный overlay, застывает на месте, `scale 1→0.7` + `opacity 0.5→0` за
|
|
||||||
**1000мс**, затем удаляется (мягкий породистый шлейф истории).
|
|
||||||
- **Прорастание линий (Edge Growth):** новая линия тянется к ФИНАЛЬНОЙ точке узла и раскрывается
|
|
||||||
`stroke-dasharray`(=длина пути) + `stroke-dashoffset`(длина→0), синхронно с разлётом узла
|
|
||||||
(`growP = текущая дистанция / финальная`) → кончик «вытягивается» из центра вслед за аватаркой.
|
|
||||||
Старые линии при этом исчезают мгновенно. Только для новых узлов; переезжающие — линия следует за ними.
|
|
||||||
- **Физика (только до-settle):** после CSS-разлёта — лёгкая радиальная пружина + отталкивание для
|
|
||||||
органичного покачивания; после фильтра физика НЕ включается (фиксация на равномерных углах).
|
|
||||||
- **Жёсткая заморозка (kill-switch):** когда сумма |vx|+|vy| < 0.03 — скорости обнуляются, координаты
|
|
||||||
округляются, `cancelAnimationFrame` (sleep). Нет «треска», батарея не страдает.
|
|
||||||
- **Обычные линии:** SVG `<path> Q` (квадратичные Безье) — тонкие матовые дуги (`stroke-width ~1.0–1.2`),
|
|
||||||
градиент с глубоким уходом в прозрачность: неон-центр `0.42` → цвет роли `0.07` у аватарки (растворяются
|
|
||||||
в фоне, не спорят с сияющими). Изгиб реагирует на скорость.
|
|
||||||
- **Сияющие связи (двухслойный «световод», Neon Layering):** два пути на одну связь — широкий размытый
|
|
||||||
GLOW (`stroke-width 4`, неон, `filter: blur(2px)`, `opacity 0.4`) + тонкий чёткий CORE (`1.5px`,
|
|
||||||
`#e0f7fc`). Линия остаётся изящной, но обретает объёмное OLED-свечение; растут оба синхронно (общий
|
|
||||||
dashoffset). Никаких бегущих импульсов.
|
|
||||||
- **Жесты:** свайп-pan с инерцией (новое касание прерывает); короткий тап — центрирование + нижний
|
|
||||||
сниппет; долгий тап — контекстное меню (в `#modal-root`, позиция по `getBoundingClientRect`); тап
|
|
||||||
по центру — профиль. Нажатия на чипы фильтров гасят `pointerdown` (stopPropagation), чтобы сцена не
|
|
||||||
перехватила указатель (`setPointerCapture`) и не «съела» click кнопки.
|
|
||||||
- **Фильтры слоёв (Все / Семья / Друзья / Сияющие):** CSS-переходы 300мс — несоответствующие узлы и их
|
|
||||||
линии гаснут НА МЕСТЕ (`opacity 0` + `scale 0.8`), оставшиеся плавно переплывают на равномерные углы,
|
|
||||||
затем жёсткая фиксация без физики (ноль тряски, мгновенный sleep).
|
|
||||||
- **Живой фон (Nebula):** под центром — глубокое размытое сине-голубое облако (`.fg-stage::before`,
|
|
||||||
`blur 80→96px`), бесконечная анимация 7с: «дышит» радиусом/яркостью и переливается индиго↔ультрамарин
|
|
||||||
(`hue-rotate`). На компоновщике (GPU), не будит rAF; подписи имён остаются контрастными.
|
|
||||||
- **Стеклянные чипы фильтров (frosted glass):** `background: rgba(255,255,255,0.03)`,
|
|
||||||
`backdrop-filter: blur(12px)`, граница `0.5px solid rgba(255,255,255,0.1)`; активный — подсвечен сине-голубым.
|
|
||||||
- **Поллиш:** «дыхание» фокуса (бесконечная CSS-анимация
|
|
||||||
размера, GPU, не будит rAF); свечение «сияющих» узлов — мягкая медленная пульсация (3.6с) многослойной
|
|
||||||
`box-shadow` + размытый ореол через SVG-фильтр `#fg-shine-glow`; тестовые фото-аватарки (`NETWORK_PHOTOS`);
|
|
||||||
хард-лимит ~90 DOM-аватарок (остальное — SVG-точки).
|
|
||||||
|
|
||||||
## Параметры тюнинга (константы в начале `force-graph.js`)
|
|
||||||
| Константа | Значение | Назначение |
|
|
||||||
|---|---|---|
|
|
||||||
| `ORBIT_MIN / ORBIT_MAX` | 150 / 240 | радиус орбиты (защитный отступ от центра — подписи не наезжают) |
|
|
||||||
| `K_RADIAL` | 0.035 | жёсткость орбитальной пружины (мягко) |
|
|
||||||
| `K_FOCUS` | 0.12 | жёсткость пружины фокуса к центру |
|
|
||||||
| `CHARGE` / `CHARGE_START_FACTOR` | 1400 / 0.45 | отталкивание (на старте ослаблено) |
|
|
||||||
| `FRICTION` / `FRICTION_BOOST` / `BOOST_FRAMES` | 0.80 / 0.94 / 42 | базовое трение / стартовая вязкость / длительность (~700мс) |
|
|
||||||
| `BLOOM_MS` / `BLOOM_STAGGER` | 900 / 40 | длительность CSS-разлёта / задержка между узлами (каскад) |
|
|
||||||
| `SLEEP_V` | 0.03 | порог суммарной |v| для заморозки |
|
|
||||||
| `FOCUS_SCALE` | 1.5 | базовый масштаб фокуса |
|
|
||||||
| `MAX_FULL_NODES` | 90 | хард-лимит полных аватарок (далее — точки) |
|
|
||||||
|
|
||||||
Прочее (вшито в код): Ghost-слой — 1000мс; CSS-переход фильтра — 300мс; пульсация сияния — 3.6с;
|
|
||||||
прорастание линий привязано к прогрессу разлёта узла (а не к отдельному таймеру).
|
|
||||||
|
|
||||||
## Локальный запуск / проверка
|
|
||||||
- Dev-сервер: `.claude/shine-ui-dev-server.cjs` (Node, порт 7321, SPA-fallback + инжект `<base href="/">`).
|
|
||||||
- Лаборатория (без бэкенда): `http://localhost:7321/network-view/lab` — мок `networkGraphUsers`,
|
|
||||||
тап по узлам переключает сети.
|
|
||||||
- Реальный путь (`/network-view`) требует живого `wss://shineup.me/ws` (локально — `ws_open_error`, это норма).
|
|
||||||
|
|
||||||
## Режим «Интерактивная паутина» (ветка `pixel-web`, эксперимент, только лаборатория)
|
|
||||||
Включается чипом «🌌 Вселенная». Дальние уровни (2-3) по умолчанию скрыты и раскрываются локально:
|
|
||||||
- **Hover-превью (наведение):** навёл мышь/палец на узел — его ветка временно выплывает; убрал —
|
|
||||||
втягивается. Реализация: `pointerover/out` (мышь) и `pointerdown/up` (палец) → `onNodeHover` →
|
|
||||||
`graph.setHover(node|null)`; узел получает флаг `hovered`.
|
|
||||||
- **Фиксация кликом (pin):** тап/клик по узлу → `graph.toggleExpand` ставит флаг `pinned` — ветка
|
|
||||||
остаётся раскрытой и после ухода курсора. Повторный клик по раскрытому узлу **сворачивает** его
|
|
||||||
(надёжный toggle: `isOpen = pinned || expandP>0.5` → сброс `pinned`+`hovered`).
|
|
||||||
- Эффективное раскрытие = `pinned || hovered` (см. `expandTargetOf`), прогресс `expandP` (~400мс).
|
|
||||||
- **Spotlight-затемнение:** пока есть закреплённая ветка, остальные тускнеют до `SPOTLIGHT_DIM=0.25`
|
|
||||||
(узлы и их линии), фокус и закреплённая/наведённая ветка — 100%. Плавно через `spotCur` (lerp).
|
|
||||||
- **Узлы 2-го уровня — полноценные аватарки:** фото-лицо (pravatar) + имя, `DEEP2_SCALE=0.62`
|
|
||||||
(≈radius 16px), `DEEP2_OPACITY=0.85`. Не «пустые кружки», а видимые друзья друзей.
|
|
||||||
- **Глобальный сброс:** тап по корню (Иван) → `collapseAll()` снимает `pinned`/`hovered` → 100% яркость.
|
|
||||||
- **Адаптивное расталкивание (collision):** раскрытая ветка усиливает отталкивание соседних узлов
|
|
||||||
1-го уровня пропорционально `expandP` (`EXPAND_REPULSION=2.4`) — кластеры разъезжаются, не накладываясь.
|
|
||||||
- **Камера-доводчик:** при фиксации ветки, если её «веер» упирается в край экрана, камера мягко
|
|
||||||
дотягивается (`glideCameraTo` → `camTargetX/Y`, lerp `CAM_GLIDE_K` в tick). Любой жест отменяет доводчик.
|
|
||||||
- **Свободный зум:** колесо мыши (`onWheel`) и щипок двумя пальцами (`activePointers`/`pinching`) —
|
|
||||||
масштаб `zoom` (0.55–2.6), «к точке» под курсором/центром щипка; мир масштабируется CSS-`scale`,
|
|
||||||
линии (отдельный SVG) пересчитываются в экранных координатах (× `zoom`).
|
|
||||||
- **Синхро-пульс линий:** сияющие/трековые «световоды» (`.fg-edge-glow`/`.fg-edge-core`) «дышат»
|
|
||||||
толщиной/размытием 3.6с — в такт ободку сияющего узла (в покое SVG не перерисовывается → синхронно).
|
|
||||||
- Мерцающие микрозвёзды 3-го уровня (`fg-star-twinkle`), хаптика (`navigator.vibrate`) на нажатие/раскрытие/натяжение.
|
|
||||||
|
|
||||||
### Умный фокус (Smart Zoom / «аквариум») — ветка `pixel-aquarium`
|
|
||||||
**Наведение** (hover/палец) на узел — лёгкое превью ветки (раскрытие на месте, без камеры).
|
|
||||||
**Клик/тап по ЛЮБОМУ узлу** — **погружение (dive)** с кинематографичным наездом:
|
|
||||||
- **Камера-полёт + зум** (`diveTo` → `diveTargetId`/`diveZoom=1.7`, лёт в `tick` с `DIVE_FLY_K` ≈600мс):
|
|
||||||
узел плавно центрируется (offset ~0) и **вырастает до единого видимого размера** `HERO_VISUAL=1.4`
|
|
||||||
независимо от уровня (`depthScale = HERO_VISUAL / baseScaleOf`); его прямые дети — до `DIVE_CHILD_VISUAL`.
|
|
||||||
- **Адаптивный радиус орбиты (фикс слипания):** дети раскладываются на кольце
|
|
||||||
`ringR = max(baseR + радиус_родителя, число_детей × 13)` — НЕ лезут на (увеличенный зумом) родитель
|
|
||||||
и друг на друга (проверено: мин. дистанция 125px, 0 наложений). Радиус растёт вместе с зумом родителя.
|
|
||||||
- **Глубина «аквариума»** (`contextTargetOf` → `depthScale`/`depthBlur`/`spotCur`, лерп): Иван и боковые
|
|
||||||
ветки **уменьшаются** (root ×0.55, фон ×0.55) + уходят в **blur 3px** + тускнеют до 0.25 → задний план.
|
|
||||||
- **Железный Spotlight (единый активный путь):** `diveTo` сначала гасит ВСЕ прежние pin/hover, затем
|
|
||||||
раскрывает только путь к новой цели. Открыто → путь Иван→…→узел = 1.0, остальное = 0.25; переключение
|
|
||||||
веток сбрасывает прежнюю; **выход/`exitDive`/тап по Ивану → ВСЕ узлы гарантированно 1.0 + камера отъезжает**.
|
|
||||||
- **Нить-крошка**: путь (`divePathSet`/`onPath`) горит ярким «световодом» — виден путь назад к Ивану.
|
|
||||||
- **Pinch-to-Zoom + LOD**: щипок/колесо меняют `zoom`; при `zoom ≥ LOD_ZOOM (1.55)` видимые точки 3-го
|
|
||||||
уровня **дорисовываются как аватарки** (`updateLod`/`setNodeLod`), при отдалении — обратно в точки.
|
|
||||||
- Глубина — фейк-3D через масштаб + CSS-`blur` (GPU), без WebGL.
|
|
||||||
|
|
||||||
**Полиш (партия 1):** веер детей раскрывается **полукругом «наружу»** (от пути назад, `DEEP_FAN`,
|
|
||||||
по `sibIndex`) — не перекрывает нить-крошку; **LOD с гистерезисом** (`LOD_ZOOM_UP=1.6`/`DOWN=1.4` — без
|
|
||||||
мигания у порога); **двойной тап по фону** и **сильный pinch-out на мин. зуме** = быстрый выход;
|
|
||||||
**префетч аватарок** детей при наведении/нырке.
|
|
||||||
|
|
||||||
**Фишки (партия 2, лаборатория):**
|
|
||||||
- **Поиск + телепорт** — строка `.fg-search`; Enter → `graph.findNode(имя)` → камера летит к узлу (dive в
|
|
||||||
«Вселенной», иначе перецентр).
|
|
||||||
- **Хлебные крошки** — `.fg-breadcrumb` «Иван › Нина › Ада» (движок шлёт `onDiveChange(path)`,
|
|
||||||
API `getDivePath()`); клик по корню — полный сброс, по предку — навигация на тот уровень.
|
|
||||||
- **Бейдж числа связей** — `.fg-node-badge` (число из `degreeById`, обновляется в `updateBadges`).
|
|
||||||
- **Цветовые кластеры** — мягкая аура узла по типу связи (CSS `is-family/friend/business/contact`).
|
|
||||||
|
|
||||||
**Линии-«жгуты» (партия 4, по референсу — плазменный композитинг):**
|
|
||||||
- **Сияющие** — ОДИН центральный S-путь (cubic Bézier) + ТРИ наложенных слоя с ОДИНАКОВЫМ `d` (объём
|
|
||||||
из толщины+размытия, НЕ из геометрии — никаких расходящихся линий):
|
|
||||||
Настоящий НЕОН (видимый ореол вокруг яркого ядра; поле/трубка в `mix-blend-mode: screen` — свет
|
|
||||||
складывается аддитивно с тёмным фоном, а в пересечениях у центра ярче — энергохаб):
|
|
||||||
- `.fg-plasma-flare` — плазменное облако: 16px, `#00bfff`, opacity 0.42, **`feGaussianBlur` stdDev=6**, screen (+ «дыхание» 3.6с);
|
|
||||||
- `.fg-plasma-tube` — направляющий свет: 6px, `#00e5ff`, opacity 0.85, **`feGaussianBlur` stdDev=2**, screen;
|
|
||||||
- `.fg-plasma-core` — ядро: 2px, `#dffaff` (светло-голубо-белое), opacity 1, без размытия.
|
|
||||||
Толщина/насыщенность подогнаны под референс (толстая яркая голубая плазма, гладкие края).
|
|
||||||
S-волна спокойная/изящная (amp до 13px). Размытие — именно SVG-фильтры (`#fg-plasma-blur6/2`), т.к.
|
|
||||||
CSS-`filter` на `<path>` в части мобильных WebView не применяется (отсюда был «плоский»/«канатный» вид).
|
|
||||||
⚠️ Это НЕ Canvas-движок (не библиотека force-graph): связи — реальные SVG `<path>`, фильтры применяются.
|
|
||||||
Прозрачность слоёв inline (× spotlight/глубину). Тяжёлый blur только у сияющих (их мало) — перф.
|
|
||||||
- **Не-сияущие** — мягкое свечение **в цвете связи** (семья/друзья/бизнес/контакт): широкая
|
|
||||||
полупрозрачная подложка + тонкое ядро, без SVG-blur (дёшево). «Похоже, но тише».
|
|
||||||
|
|
||||||
**Фишки (партия 3, лаборатория):**
|
|
||||||
- **Общие связи** — среди друзей человека один помечен как «общий» (он и твой друг тоже): золотой
|
|
||||||
ободок + ★ (CSS `is-common`; в лаб-генерации `addDeepLevels` подставляет узнаваемого друга Ивана).
|
|
||||||
- **Доступность** — визуально скрытый (`sr-only`) текстовый список графа `.fg-a11y` (центр + связи
|
|
||||||
1-го уровня) для скринридеров; обновляется в `updateA11y` при перестроении. Полезно и для реального пути.
|
|
||||||
|
|
||||||
**Автопроверки (`?fgtest`):** `js/pages/network/selftest.js` автозапускается в лаборатории при `?fgtest`,
|
|
||||||
прогоняет 17 ассертов (центровка/collision/полукруг/spotlight/переключение/LOD/поиск/крошки/бейдж/выход) через
|
|
||||||
детерминированные dev-хелперы движка `graph.debugState()` и `graph.pumpForTest()` (синхронно докручивают кадры
|
|
||||||
до покоя — не зависят от троттлинга rAF). Результат → консоль и `window.__fgTestResults`. В обычной работе не активны.
|
|
||||||
|
|
||||||
> ⚠️ Эксперименты на ветках `pixel-web` (паутина) и `pixel-aquarium` (Smart Zoom) — для отката.
|
|
||||||
> Реальный путь `/network-view` не затронут: deep-код под `tier ≥ 2` / `hasDeep`, dive — только tier≥2
|
|
||||||
> (в реальном пути их нет), depthScale/Blur по умолчанию нейтральны, `updateLod` выходит при `!hasDeep`.
|
|
||||||
|
|
||||||
## Ограничения / на будущее
|
|
||||||
- Многоуровневая глубина (друзья друзей мельче, 3-й уровень — точки), кластеры, «общие связи»
|
|
||||||
упираются в API (отдаёт только прямые связи) — требуют доработки сервера.
|
|
||||||
- Превью в простое троттлит `requestAnimationFrame` (физика не идёт между вызовами) — для замеров
|
|
||||||
прокачивать кадры; в активном табе всё работает на 60 FPS.
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Личные сообщения (messages-list) — дизайн v2
|
|
||||||
|
|
||||||
Экран ЛС — **списочная форма экрана «Связи»**: тип отношения читается через цвет
|
|
||||||
обода/ауры аватара и один правый статус. Тёмный космический фон + золотой header.
|
|
||||||
|
|
||||||
> Reference source: owner-approved chat visual reference; image asset is not yet stored in repository.
|
|
||||||
|
|
||||||
## Источник данных
|
|
||||||
- **Demo/lab:** мок `js/mock-data.js` → `directMessages` (семантические поля, цвет не хранится).
|
|
||||||
- **Прод:** те же поля придут из реальных relations (`relationFlagsForTarget` / `shineConfirmed` / `shine`).
|
|
||||||
- **Маршруты:**
|
|
||||||
- `/messages-list` — защищённый (требует сессии).
|
|
||||||
- `/messages-list/lab` — гость-демо (мок, без сети/WS, пригоден для скриншотов).
|
|
||||||
|
|
||||||
## Семантика → визуал
|
|
||||||
Решает **только** `js/pages/messages/dm-visual-resolver.js`. В данных цвет НЕ хранится.
|
|
||||||
|
|
||||||
Поля сообщения: `relationType` (contact|friend|family), `relationRole`, `isShining`,
|
|
||||||
`isConfirmed`, `hasActiveLink`, `unreadCount`, `preview`. (`toneOverride` — только для теста.)
|
|
||||||
|
|
||||||
### Цвета (значение)
|
|
||||||
| Цвет | Токен | Значение |
|
|
||||||
|------|-------|----------|
|
|
||||||
| violet | `--rel-contact` `#8C63FF` | обычный контакт (дефолт) |
|
|
||||||
| gold | `--rel-family` `#F0B82E` | семья / близкий круг / важная связь |
|
|
||||||
| celestial | `--rel-shining` `#68D8FF` | сияющий |
|
|
||||||
| emerald | `--rel-link` `#19E58A` | ТОЛЬКО активный статус «Связь» |
|
|
||||||
|
|
||||||
Обод аватара: `isShining → celestial; иначе family → gold; иначе → violet`.
|
|
||||||
**«Подтверждён» НЕ красит обод золотым** (золото = семья; подтверждение — правый статус).
|
|
||||||
|
|
||||||
### Приоритет правого статуса
|
|
||||||
`hasActiveLink → «Связь» (emerald)` > `isConfirmed → «Подтверждён» (gold shield)` > ничего.
|
|
||||||
На карточке максимум ОДИН главный статус.
|
|
||||||
|
|
||||||
### Unread
|
|
||||||
Отдельная **violet/cool сфера** (НЕ изумруд). Только при `>0`; `1–99`, далее `99+`.
|
|
||||||
Идёт после статуса, перед chevron.
|
|
||||||
|
|
||||||
## Матрица состояний (demo-мок покрывает все)
|
|
||||||
| # | relationType | shining | confirmed | link | unread | Обод | Правый статус | Бейдж |
|
|
||||||
|---|---|---|---|---|---|---|---|---|
|
|
||||||
| M01 | contact | – | ✓ | – | 0 | violet | 🛡 Подтверждён | – |
|
|
||||||
| M02 | contact | – | – | ✓ | 2 | violet | 🔗 Связь | 2 |
|
|
||||||
| M03 | contact | ✓ | – | ✓ | 5 | celestial | 🔗 Связь | 5 |
|
|
||||||
| M04 | contact | – | – | – | 0 | violet | — | – |
|
|
||||||
| M05 | family | – | ✓ | – | 0 | gold | 🛡 Подтверждён | – |
|
|
||||||
| M06 | family | – | ✓ | ✓ | 1 | gold | 🔗 Связь (приоритет link>confirmed) | 1 |
|
|
||||||
|
|
||||||
## Размеры
|
|
||||||
- Карточка: `min-height 92px`, `radius 26px`.
|
|
||||||
- Зазор списка: `8px` (flex-column).
|
|
||||||
- Аватар-обод `.dm-av`: `56px` (фото/инициалы — 50px внутри).
|
|
||||||
- Капсула «Связь»: высота `32px`, radius `16px`, изумрудный бордер, почти прозрачный fill.
|
|
||||||
- Header: grid `1fr auto 1fr` (бренд слева / title строго по центру / «+» справа), title `18px`.
|
|
||||||
|
|
||||||
## Сияющая сфера — связь с «Связями» (обязательно)
|
|
||||||
DM-сияние НЕ изобретает свой эффект, а **повторяет язык сияющего узла графа**:
|
|
||||||
- те же общие keyframes из `styles/network-graph.css`: `fg-shine-glow` (пульс box-shadow)
|
|
||||||
+ `fg-shine-halo` (дыхание ореола: scale/opacity);
|
|
||||||
- та же небесная палитра и тот же rim `rgba(150,240,255,0.62)`;
|
|
||||||
- тот же радиальный ореол (те же стопы градиента), `inset: -12px` — как у узла графа
|
|
||||||
(узел 58px ↔ аватар 56px, scale ≈ 1, отдельный масштабный коэффициент не нужен);
|
|
||||||
- `filter: blur(3.4px)` ≡ `feGaussianBlur stdDeviation="3.4"` SVG-фильтра `#fg-shine-glow`
|
|
||||||
графа. CSS-blur используется потому, что SVG-фильтр объявлен только на странице «Связи»;
|
|
||||||
- `prefers-reduced-motion` → анимации выключаются.
|
|
||||||
|
|
||||||
Разрешён только controlled scale factor; никаких отдельных hardcoded-параметров,
|
|
||||||
если они уже существуют в визуальном языке «Связей».
|
|
||||||
|
|
||||||
## Фон
|
|
||||||
Фон `.dm-screen` (`#05070A` + орбы `dm-orbs-drift`) — утверждённая база, **НЕ меняется**.
|
|
||||||
Все эффекты редизайна ограничены `.dm-*` (карточка, обод аватара, статус, бейдж, header, «+»).
|
|
||||||
Критерий: если скрыть карточки/аватары/header/nav/статусы — фон остаётся прежним.
|
|
||||||
@@ -72,6 +72,7 @@ import * as languageView from './pages/language-view.js';
|
|||||||
import * as appLogView from './pages/app-log-view.js';
|
import * as appLogView from './pages/app-log-view.js';
|
||||||
import * as pwaDiagnosticsView from './pages/pwa-diagnostics-view.js';
|
import * as pwaDiagnosticsView from './pages/pwa-diagnostics-view.js';
|
||||||
import * as solanaUsersInitView from './pages/solana-users-init-view.js';
|
import * as solanaUsersInitView from './pages/solana-users-init-view.js';
|
||||||
|
import * as solanaRpcCheckView from './pages/solana-rpc-check-view.js';
|
||||||
import * as messagesList from './pages/messages-list.js';
|
import * as messagesList from './pages/messages-list.js';
|
||||||
import * as contactSearchView from './pages/contact-search-view.js';
|
import * as contactSearchView from './pages/contact-search-view.js';
|
||||||
import * as chatView from './pages/chat-view.js?v=202607152145';
|
import * as chatView from './pages/chat-view.js?v=202607152145';
|
||||||
@@ -132,6 +133,7 @@ const routes = {
|
|||||||
'app-log-view': appLogView,
|
'app-log-view': appLogView,
|
||||||
'pwa-diagnostics-view': pwaDiagnosticsView,
|
'pwa-diagnostics-view': pwaDiagnosticsView,
|
||||||
'solana-users-init-view': solanaUsersInitView,
|
'solana-users-init-view': solanaUsersInitView,
|
||||||
|
'solana-rpc-check-view': solanaRpcCheckView,
|
||||||
'messages-list': messagesList,
|
'messages-list': messagesList,
|
||||||
'contact-search-view': contactSearchView,
|
'contact-search-view': contactSearchView,
|
||||||
'chat-view': chatView,
|
'chat-view': chatView,
|
||||||
|
|||||||
@@ -714,7 +714,7 @@ function renderNodeCard(node, heading, handlers, localNumber) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Репосты временно отключены до будущей реализации.
|
// Репосты временно отключены до будущей реализации.
|
||||||
// Точка возврата: Dev_Docs/Future_Features/2026-05-24_1140_репосты_в_каналах_и_тредах.md
|
// Точка возврата: docs/Future_Features/2026-05-24_1140_репосты_в_каналах_и_тредах.md
|
||||||
actions.append(likeButton, replyButton, shareButton);
|
actions.append(likeButton, replyButton, shareButton);
|
||||||
if (repostTarget) {
|
if (repostTarget) {
|
||||||
const originalButton = document.createElement('button');
|
const originalButton = document.createElement('button');
|
||||||
|
|||||||
@@ -1004,7 +1004,7 @@ function renderPostCard(post, {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Репосты временно отключены до будущей реализации.
|
// Репосты временно отключены до будущей реализации.
|
||||||
// Точка возврата: Dev_Docs/Future_Features/2026-05-24_1140_репосты_в_каналах_и_тредах.md
|
// Точка возврата: docs/Future_Features/2026-05-24_1140_репосты_в_каналах_и_тредах.md
|
||||||
actions.append(likeButton, replyButton);
|
actions.append(likeButton, replyButton);
|
||||||
|
|
||||||
const shareButton = document.createElement('button');
|
const shareButton = document.createElement('button');
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ export function render({ navigate }) {
|
|||||||
<button class="text-btn" type="button" id="settings-force-update-help">Клиент не обновляется?</button>
|
<button class="text-btn" type="button" id="settings-force-update-help">Клиент не обновляется?</button>
|
||||||
<button class="text-btn" type="button" id="settings-ui-error-reporting">Отправлять ошибки на сервер</button>
|
<button class="text-btn" type="button" id="settings-ui-error-reporting">Отправлять ошибки на сервер</button>
|
||||||
<button class="text-btn" type="button" id="settings-solana-users-init">Solana: init регистрации</button>
|
<button class="text-btn" type="button" id="settings-solana-users-init">Solana: init регистрации</button>
|
||||||
|
<button class="text-btn" type="button" id="settings-solana-rpc-check">Solana: проверить public RPC</button>
|
||||||
<button class="text-btn" type="button" id="settings-app-log">Лог приложения</button>
|
<button class="text-btn" type="button" id="settings-app-log">Лог приложения</button>
|
||||||
<button class="text-btn" type="button" id="settings-pwa-diagnostics">Диагностика PWA / Push</button>
|
<button class="text-btn" type="button" id="settings-pwa-diagnostics">Диагностика PWA / Push</button>
|
||||||
<button class="text-btn" type="button" id="settings-pwa-install">Как установить PWA</button>
|
<button class="text-btn" type="button" id="settings-pwa-install">Как установить PWA</button>
|
||||||
@@ -278,9 +279,11 @@ export function render({ navigate }) {
|
|||||||
const forceUpdateHelpBtn = card.querySelector('#settings-force-update-help');
|
const forceUpdateHelpBtn = card.querySelector('#settings-force-update-help');
|
||||||
const uiErrorReportingBtn = card.querySelector('#settings-ui-error-reporting');
|
const uiErrorReportingBtn = card.querySelector('#settings-ui-error-reporting');
|
||||||
const solanaUsersInitBtn = card.querySelector('#settings-solana-users-init');
|
const solanaUsersInitBtn = card.querySelector('#settings-solana-users-init');
|
||||||
|
const solanaRpcCheckBtn = card.querySelector('#settings-solana-rpc-check');
|
||||||
|
|
||||||
appLogBtn?.addEventListener('click', () => navigate('app-log-view'));
|
appLogBtn?.addEventListener('click', () => navigate('app-log-view'));
|
||||||
solanaUsersInitBtn?.addEventListener('click', () => navigate('solana-users-init-view'));
|
solanaUsersInitBtn?.addEventListener('click', () => navigate('solana-users-init-view'));
|
||||||
|
solanaRpcCheckBtn?.addEventListener('click', () => navigate('solana-rpc-check-view'));
|
||||||
diagnosticsBtn?.addEventListener('click', () => navigate('pwa-diagnostics-view'));
|
diagnosticsBtn?.addEventListener('click', () => navigate('pwa-diagnostics-view'));
|
||||||
uploadAvatarBtn?.addEventListener('click', () => {
|
uploadAvatarBtn?.addEventListener('click', () => {
|
||||||
openDeveloperAvatarUploadModal({
|
openDeveloperAvatarUploadModal({
|
||||||
|
|||||||
@@ -162,6 +162,9 @@ export function render({ navigate }) {
|
|||||||
state.registrationDraft.sessionId = '';
|
state.registrationDraft.sessionId = '';
|
||||||
state.registrationDraft.pendingKeyBundle = null;
|
state.registrationDraft.pendingKeyBundle = null;
|
||||||
state.registrationDraft.pendingSessionMaterial = null;
|
state.registrationDraft.pendingSessionMaterial = null;
|
||||||
|
state.registrationDraft.preGeneratedKeyBundle = null;
|
||||||
|
state.registrationPayment.walletAddress = '';
|
||||||
|
state.registrationPayment.balanceSOL = '0.0000';
|
||||||
|
|
||||||
await refreshSessions();
|
await refreshSessions();
|
||||||
setAuthInfo(isLoginFlow
|
setAuthInfo(isLoginFlow
|
||||||
|
|||||||
@@ -0,0 +1,258 @@
|
|||||||
|
import { renderHeader } from '../components/header.js';
|
||||||
|
|
||||||
|
export const pageMeta = { id: 'solana-rpc-check-view', title: 'Проверка Solana RPC' };
|
||||||
|
|
||||||
|
const MAINNET_RPC_ENDPOINTS = [
|
||||||
|
{ name: 'GetBlock', url: 'https://go.getblock.us/86aac42ad4484f3c813079afc201451c' },
|
||||||
|
{ name: 'PublicNode', url: 'https://solana-rpc.publicnode.com' },
|
||||||
|
{ name: 'BlockEden', url: 'https://api.blockeden.xyz/solana/KeCh6p22EX5AeRHxMSmc' },
|
||||||
|
{ name: 'dRPC', url: 'https://solana.drpc.org/' },
|
||||||
|
{ name: 'Grove', url: 'https://solana.rpc.grove.city/v1/01fdb492' },
|
||||||
|
{ name: 'LeoRPC', url: 'https://solana.leorpc.com/?api_key=FREE' },
|
||||||
|
{ name: 'OnFinality', url: 'https://solana.api.onfinality.io/public' },
|
||||||
|
{ name: 'Solana Foundation', url: 'https://api.mainnet-beta.solana.com' },
|
||||||
|
{ name: 'Solana Vibe Station', url: 'https://public.rpc.solanavibestation.com/' },
|
||||||
|
{ name: 'TheRPC', url: 'https://solana.therpc.io' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const RPC_PAYLOAD = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 1,
|
||||||
|
method: 'getVersion',
|
||||||
|
params: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
function classifyResult(result) {
|
||||||
|
if (!result) return { label: 'Нет данных', tone: 'neutral' };
|
||||||
|
if (result.ok) return { label: 'Работает в браузере', tone: 'ok' };
|
||||||
|
if (result.httpStatus === 402) return { label: 'Требует платный план', tone: 'bad' };
|
||||||
|
if (result.httpStatus === 403) return { label: 'Доступ запрещён', tone: 'bad' };
|
||||||
|
if (result.httpStatus === 429) return { label: 'Упёрлась в rate limit', tone: 'warn' };
|
||||||
|
if (result.httpStatus >= 500) return { label: 'Ошибка на стороне RPC', tone: 'bad' };
|
||||||
|
if (result.errorCode === 35) return { label: 'Free tier не даёт mainnet', tone: 'bad' };
|
||||||
|
if (result.networkError) return { label: 'Не проходит из браузера', tone: 'bad' };
|
||||||
|
return { label: 'Отвечает, но не подходит', tone: 'warn' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortenText(value, max = 140) {
|
||||||
|
const text = String(value || '').trim();
|
||||||
|
if (!text) return '—';
|
||||||
|
return text.length > max ? `${text.slice(0, max)}...` : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testEndpoint(url, timeoutMs = 12000) {
|
||||||
|
const startedAt = performance.now();
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
mode: 'cors',
|
||||||
|
cache: 'no-store',
|
||||||
|
credentials: 'omit',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(RPC_PAYLOAD),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
const elapsedMs = Math.round(performance.now() - startedAt);
|
||||||
|
let data = null;
|
||||||
|
let raw = '';
|
||||||
|
try {
|
||||||
|
raw = await response.text();
|
||||||
|
data = raw ? JSON.parse(raw) : null;
|
||||||
|
} catch {
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
ok: response.ok && !!data?.result,
|
||||||
|
httpStatus: response.status,
|
||||||
|
elapsedMs,
|
||||||
|
result: data?.result || null,
|
||||||
|
errorCode: data?.error?.code ?? null,
|
||||||
|
errorMessage: data?.error?.message || '',
|
||||||
|
raw: shortenText(raw, 180),
|
||||||
|
networkError: '',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const elapsedMs = Math.round(performance.now() - startedAt);
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
ok: false,
|
||||||
|
httpStatus: 0,
|
||||||
|
elapsedMs,
|
||||||
|
result: null,
|
||||||
|
errorCode: null,
|
||||||
|
errorMessage: '',
|
||||||
|
raw: '',
|
||||||
|
networkError: error?.name === 'AbortError' ? 'Timeout' : (error?.message || 'Network error'),
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeResultCard(endpoint) {
|
||||||
|
const card = document.createElement('article');
|
||||||
|
card.className = 'card stack solana-rpc-card';
|
||||||
|
|
||||||
|
const head = document.createElement('div');
|
||||||
|
head.className = 'solana-rpc-card-head';
|
||||||
|
|
||||||
|
const title = document.createElement('strong');
|
||||||
|
title.textContent = endpoint.name;
|
||||||
|
|
||||||
|
const badge = document.createElement('span');
|
||||||
|
badge.className = 'badge alt solana-rpc-badge';
|
||||||
|
badge.textContent = 'Ожидает проверки';
|
||||||
|
|
||||||
|
head.append(title, badge);
|
||||||
|
|
||||||
|
const endpointLine = document.createElement('a');
|
||||||
|
endpointLine.className = 'solana-rpc-url';
|
||||||
|
endpointLine.href = endpoint.url;
|
||||||
|
endpointLine.target = '_blank';
|
||||||
|
endpointLine.rel = 'noopener noreferrer';
|
||||||
|
endpointLine.textContent = endpoint.url;
|
||||||
|
|
||||||
|
const meta = document.createElement('div');
|
||||||
|
meta.className = 'solana-rpc-meta';
|
||||||
|
|
||||||
|
const statusLine = document.createElement('div');
|
||||||
|
statusLine.className = 'meta-muted';
|
||||||
|
statusLine.textContent = 'Ещё не проверяли.';
|
||||||
|
|
||||||
|
const details = document.createElement('div');
|
||||||
|
details.className = 'meta-muted solana-rpc-details';
|
||||||
|
details.textContent = '—';
|
||||||
|
|
||||||
|
meta.append(statusLine, details);
|
||||||
|
card.append(head, endpointLine, meta);
|
||||||
|
|
||||||
|
return { card, badge, statusLine, details };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function render({ navigate }) {
|
||||||
|
const screen = document.createElement('section');
|
||||||
|
screen.className = 'stack';
|
||||||
|
|
||||||
|
const intro = document.createElement('div');
|
||||||
|
intro.className = 'card stack';
|
||||||
|
intro.innerHTML = `
|
||||||
|
<strong>Проверка mainnet RPC из браузера</strong>
|
||||||
|
<p class="meta-muted">
|
||||||
|
Этот экран делает реальный browser fetch к публичным Solana mainnet RPC.
|
||||||
|
Если ответ приходит прямо здесь, значит нода годится для клиентского UI без промежуточного сервера.
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const controls = document.createElement('div');
|
||||||
|
controls.className = 'auth-footer-actions';
|
||||||
|
|
||||||
|
const runBtn = document.createElement('button');
|
||||||
|
runBtn.className = 'primary-btn';
|
||||||
|
runBtn.type = 'button';
|
||||||
|
runBtn.textContent = 'Проверить все ноды';
|
||||||
|
|
||||||
|
const resetBtn = document.createElement('button');
|
||||||
|
resetBtn.className = 'ghost-btn';
|
||||||
|
resetBtn.type = 'button';
|
||||||
|
resetBtn.textContent = 'Сбросить';
|
||||||
|
|
||||||
|
controls.append(runBtn, resetBtn);
|
||||||
|
intro.append(controls);
|
||||||
|
|
||||||
|
const summary = document.createElement('p');
|
||||||
|
summary.className = 'status-line';
|
||||||
|
summary.textContent = 'Пока проверки не запускались.';
|
||||||
|
|
||||||
|
const grid = document.createElement('div');
|
||||||
|
grid.className = 'stack';
|
||||||
|
|
||||||
|
const cards = MAINNET_RPC_ENDPOINTS.map((endpoint) => {
|
||||||
|
const ui = makeResultCard(endpoint);
|
||||||
|
grid.append(ui.card);
|
||||||
|
return { endpoint, ui };
|
||||||
|
});
|
||||||
|
|
||||||
|
let runToken = 0;
|
||||||
|
|
||||||
|
const resetState = () => {
|
||||||
|
runToken += 1;
|
||||||
|
summary.className = 'status-line';
|
||||||
|
summary.textContent = 'Пока проверки не запускались.';
|
||||||
|
cards.forEach(({ ui }) => {
|
||||||
|
ui.badge.className = 'badge alt solana-rpc-badge';
|
||||||
|
ui.badge.textContent = 'Ожидает проверки';
|
||||||
|
ui.statusLine.textContent = 'Ещё не проверяли.';
|
||||||
|
ui.details.textContent = '—';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const runChecks = async () => {
|
||||||
|
const token = ++runToken;
|
||||||
|
runBtn.disabled = true;
|
||||||
|
resetBtn.disabled = true;
|
||||||
|
summary.className = 'status-line';
|
||||||
|
summary.textContent = 'Проверяем mainnet RPC из браузера...';
|
||||||
|
|
||||||
|
let okCount = 0;
|
||||||
|
let warnCount = 0;
|
||||||
|
let badCount = 0;
|
||||||
|
|
||||||
|
const tasks = cards.map(async ({ endpoint, ui }) => {
|
||||||
|
ui.badge.className = 'badge alt solana-rpc-badge';
|
||||||
|
ui.badge.textContent = 'Проверяем...';
|
||||||
|
ui.statusLine.textContent = 'Отправляем JSON-RPC запрос getVersion...';
|
||||||
|
ui.details.textContent = 'Ждём ответ.';
|
||||||
|
|
||||||
|
const result = await testEndpoint(endpoint.url);
|
||||||
|
if (token !== runToken) return;
|
||||||
|
|
||||||
|
const verdict = classifyResult(result);
|
||||||
|
if (verdict.tone === 'ok') okCount += 1;
|
||||||
|
else if (verdict.tone === 'warn') warnCount += 1;
|
||||||
|
else if (verdict.tone === 'bad') badCount += 1;
|
||||||
|
|
||||||
|
ui.badge.className = `badge solana-rpc-badge is-${verdict.tone}`;
|
||||||
|
ui.badge.textContent = verdict.label;
|
||||||
|
ui.statusLine.textContent = `HTTP ${result.httpStatus || 'ERR'} • ${result.elapsedMs} мс`;
|
||||||
|
if (result.ok) {
|
||||||
|
ui.details.textContent = `solana-core: ${result.result?.['solana-core'] || 'unknown'} • feature-set: ${result.result?.['feature-set'] || 'unknown'}`;
|
||||||
|
} else if (result.networkError) {
|
||||||
|
ui.details.textContent = `Browser error: ${result.networkError}`;
|
||||||
|
} else if (result.errorMessage) {
|
||||||
|
ui.details.textContent = shortenText(result.errorMessage, 180);
|
||||||
|
} else {
|
||||||
|
ui.details.textContent = result.raw || 'Нода ответила без пригодного JSON-RPC result.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(tasks);
|
||||||
|
if (token !== runToken) return;
|
||||||
|
|
||||||
|
summary.className = 'status-line is-available';
|
||||||
|
summary.textContent = `Готово. Для браузера подходят: ${okCount}. С оговорками: ${warnCount}. Не подходят: ${badCount}.`;
|
||||||
|
runBtn.disabled = false;
|
||||||
|
resetBtn.disabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
runBtn.addEventListener('click', () => {
|
||||||
|
void runChecks();
|
||||||
|
});
|
||||||
|
resetBtn.addEventListener('click', resetState);
|
||||||
|
|
||||||
|
screen.append(
|
||||||
|
renderHeader({
|
||||||
|
title: 'Проверка Solana RPC',
|
||||||
|
leftAction: { label: '←', onClick: () => navigate('developer-settings-view') },
|
||||||
|
}),
|
||||||
|
intro,
|
||||||
|
summary,
|
||||||
|
grid,
|
||||||
|
);
|
||||||
|
|
||||||
|
void runChecks();
|
||||||
|
|
||||||
|
return screen;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Документ описывает целевой формат пользовательской PDA-записи `user_pda` для Solana-программы `shine_users`.
|
Документ описывает целевой формат пользовательской PDA-записи `user_pda` для Solana-программы `shine_users`.
|
||||||
|
|
||||||
Это не формат основного блокчейна SHiNE и не документация по `AddBlock`. Основной блокчейн SHiNE описан отдельно в `Dev_Docs/Blockchain/`.
|
Это не формат основного блокчейна SHiNE и не документация по `AddBlock`. Основной блокчейн SHiNE описан отдельно в `docs/Blockchain/`.
|
||||||
|
|
||||||
Статус документа: итоговый согласованный формат, к которому приведены `create_user_pda`, `update_user_pda` и тестовый сериализатор Solana-модуля.
|
Статус документа: итоговый согласованный формат, к которому приведены `create_user_pda`, `update_user_pda` и тестовый сериализатор Solana-модуля.
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,6 @@
|
|||||||
|
|
||||||
Актуальный подробный сценарий тестирования и его статус ведётся в:
|
Актуальный подробный сценарий тестирования и его статус ведётся в:
|
||||||
|
|
||||||
- `Dev_Docs/Pending_Features/2026-06-06_1659_shine_payments_e2e_перепись_и_q3.md`
|
- `docs/Pending_Features/2026-06-06_1659_shine_payments_e2e_перепись_и_q3.md`
|
||||||
|
|
||||||
Этот файл в `shine-solana/shine/doc/` оставлен как постоянная заметка, что у программы есть отдельный e2e-план проверки.
|
Этот файл в `shine-solana/shine/doc/` оставлен как постоянная заметка, что у программы есть отдельный e2e-план проверки.
|
||||||
|
|||||||
Reference in New Issue
Block a user