From 217b3747896b912e736bf87a0c49a187935c0d27 Mon Sep 17 00:00:00 2001 From: vsecoder Date: Wed, 22 Apr 2026 16:05:06 +0300 Subject: [PATCH] docs: add v2.2.0 multi-device + desktop Electron roadmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Living doc that captures: * Multi-device plan — per-device X25519 keys + on-chain device registry (LINK_DEVICE / UNLINK_DEVICE tx types, sender fan-out, revoke + wipe). Broken into four PRs (#1 chain, #2 client fan-out, #3 pairing flow, #4 desktop shell) tagged v2.2.0-alphaN. * Desktop Electron client — 3-panel layout, section map, reused lib/, keybinds, packaging targets. Queued after v2.2.0-alpha3. * Known smaller tails — gossip dup log noise, seed self-announce fallback, group chats (post-v2.2.0), reputation-weighted leader. --- docs/ROADMAP.md | 219 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 docs/ROADMAP.md diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..4652c10 --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,219 @@ +# Roadmap + +Living doc — «куда идём дальше». Два активных вектора: **v2.2.0 multi-device** +и **desktop Electron client**. Всё что tentative — помечено `?`; всё что +решено и принято к работе — без знаков. + +--- + +## v2.2.0 — Multi-device (per-device X25519, on-chain device registry) + +### Проблема + +Сейчас у пользователя одна пара X25519 (хранится в `keyFile`), и relay-mailbox — +queue с DELETE-после-чтения. При двух устройствах: + +- Конверт забирает то устройство, что первое дёрнуло `/relay/inbox` — второе + теряет сообщение навсегда. +- История чатов per-device, не синхронизируется (`AsyncStorage` на каждом + устройстве свой). + +On-chain часть (posts, follows, tx, wallet) работает нормально — оба устройства +читают один чейн и видят одно и то же. Чинить надо только мессенджер. + +### Решение — путь А (Signal-style): X25519 на устройство + device registry + +1. **Master identity = Ed25519** (как сейчас). Подписывает tx, владеет + балансом, — один на всю identity. +2. **X25519 — per-device**. Каждое устройство при первом старте генерит + свою пару. Relay-mailbox остаётся queue'ом, но адресуется по + **устройству**, не по identity. +3. **Device registry on-chain**: новые tx-типы `LINK_DEVICE` / `UNLINK_DEVICE`, + подписываются master Ed25519, публикуют/отзывают связку + `(master_pub → device_x25519_pub, device_name)`. +4. **Sender-side fan-out**: при отправке — один envelope на каждое + активное устройство получателя. Мессадж → N конвертов. +5. **Revoke**: master подписывает `UNLINK_DEVICE`, клиент, обнаруживший + свой `x25519_pub` в revoked — wipe'ит локальную БД (zeroize master + Ed25519 + delete chats). + +### План работ (PR-by-PR) + +#### PR #1 — Chain-side (backend) — _v2.2.0-alpha1_ + +- [ ] `blockchain/types.go`: `EventLinkDevice`, `EventUnlinkDevice` + + payload structs `LinkDevicePayload`, `UnlinkDevicePayload`. +- [ ] `blockchain/chain.go`: applyTx-ветки, state persistence: + - `prefixDevice + x25519_pub → DeviceRecord{owner, name, added_at, revoked_at?}`. + - Обратный индекс `prefixDevicesByOwner + master_pub → [x25519_pub, …]`. +- [ ] Константа `MaxDevicesPerOwner = 10`. +- [ ] Validation: + - Tx подписана master'ом; подпись matches `payload.Owner`. + - `x25519_pub` уникален в registry (не занят другим master'ом). + - `device_name` ≤ 64 символов; printable. + - При `UNLINK_DEVICE`: запись существует, owner совпадает с signer'ом. +- [ ] HTTP: `GET /api/devices/{master_pub}` → `[{x25519_pub, device_name, added_at}, …]`, + фильтрует revoked. +- [ ] В `/api/identity/{pub}` добавить поле `device_count`. +- [ ] Unit-тесты: + - Happy path link + list. + - Unlink → list сократился. + - Лимит `MaxDevicesPerOwner`. + - Чужая подпись → reject. + - Duplicate x25519_pub → reject. +- [ ] Swagger + `docs/api/devices.md`. + +Совместимость: старые клиенты, которые не обновили identity, продолжают +работать по old-schema (envelope на published X25519 из identity). Sender +fall-back'ит на identity.x25519, если `device_count == 0`. + +#### PR #2 — Client fan-out (mobile) — _v2.2.0-alpha2_ + +- [ ] `lib/api.ts`: `fetchDevices(masterPub): Promise`, + с кэшем в zustand store + инвалидацией по WS-ивенту `tx:LINK_DEVICE|UNLINK_DEVICE`. +- [ ] `chats/[id].tsx` → `sendCore`: `Promise.all` по `devices[]`; + если `devices.length == 0` — fall-back на identity.x25519 (legacy path). +- [ ] При первом старте (онбординг): если identity ещё не зарегистрирована — + `PUBLISH_KEY` tx + `LINK_DEVICE` tx (**этот** девайс как первый в списке). +- [ ] UI: в chat-list tile бейдж `2/2` на своих сообщениях (доставлено + на N устройств получателя). +- [ ] Тест: два клиента на одну identity, отправляем на contact — оба + получают. + +#### PR #3 — Devices screen + pairing flow (mobile + desktop) — _v2.2.0-alpha3_ + +- [ ] Settings → **Devices**: список активных, unlink-кнопка у каждого + (кроме `this device`). +- [ ] «Link new device» flow: + - Новое устройство генерит свой X25519, показывает QR + `{x25519_pub, device_name, nonce, rendezvous_id}`. + - Старое сканирует → Confirm → подписывает `LINK_DEVICE` tx + + шлёт через relay envelope `{master_ed25519_priv, recent_history}` — + encrypted for new device's X25519, TTL ≤ 60 сек. + - Новое читает envelope, сохраняет master + history, готово. +- [ ] Self-wipe при обнаружении своего `x25519_pub` в revoked: + zeroize master, clear AsyncStorage/keychain, redirect на onboarding. + +#### PR #4 — Desktop Electron shell — _v2.2.0-rc1_ + +См. отдельный раздел ниже. + +### Нерешённые вопросы + +- **QR-pairing UX на desktop'е**: у ноутбука камеры часто нет. Вариант: + master на phone шлёт invite-envelope по pre-shared secret (6-digit code, + вводится в обе стороны), без QR. Подумать после PR #2. +- **History-sync backup формат**: JSON export всей ChatStorage shreds'ов? + Ограничение по размеру? TTL у backup-конверта? — в PR #3. +- **Revoked-device-wipe race**: если устройство оффлайн 6 месяцев, потом + поднимается — первое что видит это свой revoked. Должен успеть wipe'нуться + **до** попытки послать tx с master-ключом. Продумать порядок bootstrap'а. + +--- + +## Desktop client (Electron) + +Цель: 1:1 функциональный паритет с мобильным, десктопная эргономика, keyboard-first. + +### Архитектура + +- **Electron** + **React (Vite)** + **TypeScript**. +- 80% `lib/` переиспользуется из `client-app/lib/` (api, feed, crypto, store, types). +- Нативные модули: `electron-store` (+ `safeStorage`) для keys, `clipboard`, + `Notification`, `dialog.showOpenDialog`. +- Custom title bar (frame-less), 3-panel layout. + +### Экраны + +Full design — `docs/desktop-design.md` (TODO). Sections: + +- **Messages** — список чатов + conversation panel. Saved Messages pinned top. +- **Feed** — tabs (For You / Following / Trending 24h / 7d / Hashtag) + + post list + thread panel. +- **Wallet** — account overview + tx history + tx detail. +- **Contacts** — grouped list (All / Online / Blocked / Requests) + contact profile. +- **Settings** — grouped: Node, Identity, **Devices**, Privacy, Feed, Notifications, + Advanced, About. +- **Profile** (внизу nav) — self profile; чужой — в detail pane. +- **Auth/Onboarding** — Welcome / Create / Import / Pair-with-phone (QR/code). + +### Стилистика (переносим) + +Чёрный `#000`, карточки `#0a0a0a`, бордеры `#1f1f1f`, текст `#fff`/`#8b8b8b`. +Синий accent `#1d9bf0`, оранж warning `#f0b35a`. Синий bookmark-avatar для Saved Messages. + +### Не переносим + +Все React-Native-анимации (Pressable-animations, `scaleY: -1`, gesture-handler +long-press). На десктопе — правый клик, hover-states, keyboard shortcuts, +обычный scroll вместо inverted FlatList. + +### Global keybinds + +| Key | Action | +|---|---| +| `Ctrl/Cmd+1..5` | Переключение секций | +| `Ctrl/Cmd+N` | Новый пост / новый контакт (контекстно) | +| `Ctrl/Cmd+K` | Global search | +| `Ctrl/Cmd+F` | Search в текущей list pane | +| `Ctrl/Cmd+,` | Settings | +| `Ctrl/Cmd+Enter` | Send (chat / publish post) | +| `Esc` | Close modal / cancel selection | + +### Структура папок + +``` +desktop/ +├── electron/ +│ ├── main.ts # BrowserWindow, IPC, auto-update +│ ├── preload.ts # contextBridge — safe API +│ ├── menu.ts # native menu bar +│ └── deep-link.ts # dchain:// protocol handler +├── src/ # renderer +│ ├── App.tsx +│ ├── shell/ # nav, status bar, title bar +│ ├── sections/ +│ │ ├── messages/ +│ │ ├── feed/ +│ │ ├── wallet/ +│ │ ├── contacts/ +│ │ ├── settings/ +│ │ └── profile/ +│ ├── modals/ +│ ├── auth/ +│ ├── lib/ # reuse из client-app/lib +│ └── styles/ +└── package.json +``` + +### План работ (отдельно, после v2.2.0-alpha3) + +- [ ] Boilerplate: Electron + Vite + React + TS, frame-less window, 3-panel shell. +- [ ] Сопрягание `lib/` из client-app (заменить expo-* на Electron-эквиваленты). +- [ ] Sections по порядку: Messages → Feed → Wallet → Contacts → Settings → Profile. +- [ ] Multi-device pairing flow (использует v2.2.0 registry). +- [ ] Auto-update через electron-updater или через тот же `/api/update-check`. +- [ ] Packaging: `electron-builder` → `.dmg`, `.exe`, `.AppImage`, `.deb`. + +### Открытые вопросы (desktop) + +- **Auto-update channel**: гнать через Gitea releases (как node) или отдельный S3/Gitea-attachments? +- **Sandbox**: desktop хранит master Ed25519 — обязательно `safeStorage` (macOS Keychain, Windows DPAPI, libsecret на Linux). На Linux без libsecret — fallback на plaintext + warning. +- **Deep-link**: `dchain://chat/` для шаринга профилей/постов из браузера. + +--- + +## Меньшие хвосты (неприоритетно) + +- `prev_hash mismatch` при двойной gossip-доставке — понизить до Debug уровня, + не пугать операторов (см. history с joiner-node bring-up). +- `/api/network-info.peers` у seed'а возвращает `[]` если + `DCHAIN_ANNOUNCE` не задан — добавить fallback: + если announce пустой, публиковать *первый listen-addr + свой peer_id* + (не `127.0.0.1`), чтобы joiner'ы не упирались в `peers:[]`. +- **Groups (3+ участников)** в чатах — тип `Contact.kind == 'group'` + в клиенте есть, а на backend'е не реализован. После v2.2.0, потому что + pairwise messaging ↔ group messaging на multi-device — это другой + математический слой (MLS или Sender Keys). +- Repa как weight в selection-lider PBFT — сейчас только scoring; для + настоящего proof-of-reputation нужно переписать leader-rotation.