docs: add v2.2.0 multi-device + desktop Electron roadmap
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.
This commit is contained in:
219
docs/ROADMAP.md
Normal file
219
docs/ROADMAP.md
Normal file
@@ -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<DeviceRecord[]>`,
|
||||||
|
с кэшем в 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/<pub>` для шаринга профилей/постов из браузера.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Меньшие хвосты (неприоритетно)
|
||||||
|
|
||||||
|
- `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.
|
||||||
Reference in New Issue
Block a user