Files
dchain/docs/ROADMAP.md
vsecoder 217b374789 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.
2026-04-22 16:05:06 +03:00

220 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.