Closes the v2.2.0 roadmap. Desktop client is feature-complete and
ready for first installer builds.
Contact request flow (fills a real gap flagged by the user):
* lib/tx.ts grows buildContactRequestTx / buildAcceptContactTx /
buildBlockContactTx with canonical bytes matching mobile.
* lib/api.ts: fetchContactRequests + ContactRequestRaw.
* New contact modal — sections/contacts/NewContactModal.tsx — resolves
@username / DC-address / hex pub via resolveAccount, shows identity
preview (incl. "has encryption key / key not published" hint),
fee tier picker (5k / 10k / 50k µT), optional 280-char intro,
balance guard.
* Requests inbox — sections/contacts/RequestsList.tsx — polled every
15 s via /relay/contacts, filters pending, Accept submits
ACCEPT_CONTACT + adds the peer to local contacts with their
identity.x25519_pub pre-cached, Block submits BLOCK_CONTACT.
* ContactsList grows a two-tab header (Contacts / Requests with a
pending-count badge) + "+ New" button next to the filter input.
Auto-update:
* hooks/useUpdateCheck.ts — polls /api/update-check on mount and
every 6 hours; loose semver compares the Gitea release tag
against this build's app.version (from Electron IPC), ignores
the node's own update_available flag (it compares vs. the node,
not the desktop).
* shell/UpdateBanner.tsx — thin strip above the status bar with
the new tag, Download button (opens the release URL in the
default browser), and a dismiss-for-this-tag × so once-seen
updates don't nag.
Packaging — electron-builder config tightened:
* artifactName pattern includes version + os + arch.
* Mac: hardenedRuntime on, dmg + zip outputs, social-networking
category.
* Windows: NSIS (full installer, per-user or per-machine) +
portable exe.
* Linux: AppImage + deb.
* Strip source maps and test folders from the asar.
* publish: null — no auto-publisher yet; Gitea releases are
uploaded manually for now.
* directories.output = release/, directories.buildResources =
resources/ so icons land in a predictable place once we add them.
Version bumped to 2.2.0 in package.json. docs/ROADMAP.md marks
v2.2.0 row complete; remaining work (attachments, code signing,
group chats) moved to a post-v2.2.0 bucket.
Completes the desktop feature surface ahead of the v2.2.0 tag. Only
auto-update + packaging remain.
Settings — now two-paned (nav on the left, pages on the right):
* NodePage — URL ping-on-commit + API token field.
* IdentityPage — pub key / X25519 pub, Export (safe-save dialog) /
Import (open dialog + wipe + replace) / Delete identity.
* DevicesPage — full multi-device UI: list every active device with
a THIS DEVICE badge; Unlink button on every other row submits
UNLINK_DEVICE + optimistic local remove; Link new device modal
takes {code, device key, name}, submits LINK_DEVICE, then ships
the handshake envelope (master Ed25519 priv encrypted for the
new X25519) — same protocol as mobile's primary-device modal.
* AboutPage — version, platform, Gitea links.
* store.settingsPage discriminated union keeps selection across
section switches.
Contacts section (now real):
* ContactsList — alphabetical, filter-as-you-type; each row shows
avatar letter + name + short address.
* ContactsDetail — profile card (username/alias/pub) + Open chat /
View posts / Copy address actions + stats grid
(Balance, Devices, Encryption, Added) + Identity card with
DC address, username, published X25519, device_count.
* store.selectedContact persists across navigation.
Profile section (expanded):
* ProfileList — big avatar + pub key + contacts count.
* ProfileDetail — balance hero, quick actions (My posts →
feed author wall, Manage devices → Settings→Devices, Copy
address), Identity card, inline Linked devices list with a
THIS DEVICE badge matching the Settings page.
Receive modal — canvas QR via `qrcode` (new dep, ~5 KB gzipped),
white-on-transparent so it sits inside the same black modal chrome.
Global keybinds (useGlobalKeybinds hook mounted in Shell):
* Ctrl/Cmd+W — close the current conversation (drops activeChat,
keeps section). Does NOT close the window.
* Ctrl/Cmd+K — jump to Contacts.
* Ctrl/Cmd+, — Settings.
Each guards against being in a text field so typing `k,` in a
composer / search doesn't hijack.
docs/ROADMAP.md — rc1 row flipped to done; v2.2.0 narrows to
auto-update + packaging + optional attachments in Compose.
PR #4 of the multi-device roadmap — desktop client groundwork. The shell
compiles and runs end-to-end on top of a v2.2.0 node; sections are
placeholders that later alphas fill in with real chat / feed / wallet /
contacts / settings content shared with the mobile client-app.
Scaffold:
* Vite + React + TypeScript renderer; Electron main/preload TS
compiled via a separate tsconfig.
* npm scripts — `dev` (concurrent Vite + Electron), `build`
(installer via electron-builder), `typecheck`.
* electron-builder targets: .dmg / .exe / .AppImage + .deb.
* CSP pins script-src 'self'; connect-src left open so the renderer
can hit any configured node.
Electron main + preload:
* Frame-less window, hiddenInset on macOS, custom-overlay on Windows,
drag region via CSS -webkit-app-region: drag on our TitleBar.
* contextIsolation on, nodeIntegration off, sandbox off (needed for
safeStorage in preload).
* window.dchain.keyfile.{load,save,delete,encryptionAvailable} —
keyfile lives in the OS keychain via Electron safeStorage, with a
plaintext fallback for OSes without an encryption backend.
* window.dchain.dialog.{openFile,saveFile}, .fs.{readText,writeText},
.app.{version,platform}. Everything else still goes over plain
fetch() in the renderer.
Shell (src/shell/):
* TitleBar — draggable 32px strip; DChain brand.
* NavBar — left 72px rail, six sections + Cmd+1..5 keybinds.
* StatusBar — ● online/connecting/offline dot, node URL, current
chain height (polls /api/netstats every 5s).
* Shell — composes the 3 panes; picks { List, Detail } by active
section.
Sections (all stubs — filling in alpha5+):
* Messages, Feed, Contacts, Profile — SectionPlaceholder with notes.
* Wallet — shows the balance reading from /api/address/{pub} as a
first real data binding.
* Settings — node-URL card with live ping + commit, identity card
(shows pub key), about card (reads Electron app.version via IPC).
Auth (src/auth/Welcome.tsx):
* Create — generates Ed25519 + X25519 via tweetnacl, saves via IPC.
* Import — Electron dialog.openFile → parses node.json → saves.
* Pair — stub routed; real poll loop reuses the mobile flow in
alpha5.
Lib (src/lib/):
* types.ts — KeyFile / Contact / Message / NodeSettings mirroring
client-app wire formats.
* storage.ts — keyfile via IPC, settings + contacts + device-registered
marker via localStorage.
* api.ts — fetch wrapper with setNodeUrl + onNodeUrlChange;
getNetStats, getIdentity, fetchDevices, getBalance bindings.
* store.ts — zustand { booted, keyFile, settings, contacts, section }.
docs/ROADMAP.md — desktop subsection updated with per-alpha breakdown.
Next (alpha5): Messages section wired to the relay mailbox, full
conversation view, and the pairing poll loop.
README
- Mention social feed in the one-line description and feature bullets
- Add relay + feed endpoint tables to the API overview (was
previously empty on messaging)
- List media/ package in the repo structure
docs/api/
- New docs/api/feed.md: full reference for /feed/publish, fetch,
stats, view, author, timeline, trending, foryou, hashtag; all
on-chain CREATE_POST / DELETE_POST / FOLLOW / LIKE payloads;
fee economics; server-side scrubbing contract.
- docs/api/relay.md rewritten: /relay/broadcast is now the primary
E2E path with a complete envelope schema; /relay/send kept but
flagged ⚠ NOT E2E; DELETE /relay/inbox/{id} documented with the
new Ed25519 signed-auth body.
- docs/api/README.md index: added feed.md row.
docs/architecture.md
- L2 Transport layer description updated to include the feed
mailbox alongside the 1:1 relay mailbox.
- New "Социальная лента (v2.0.0)" section right after the 1:1
message flow: ASCII diagram of publish + on-chain commit +
timeline fetch, economic summary, metadata-scrub summary.
docs/node/README.md
- Removed stale chan:/chan-member: keys from the BadgerDB schema
reference; replaced with the v2.0.0 feed keys (post:,
postbyauthor:, follow:, followin:, like:, likecount:).
docs/update-system.md
- Example features[] array updated to match the actual node output
(channels_v1 removed, feed_v2 / media_scrub / relay_broadcast added).
Node feature flags
- api_well_known_version.go: dropped channels_v1 tag (the
/api/channels/:id endpoint was removed in the feed refactor);
added feed_v2, media_scrub, relay_broadcast so clients can
feature-detect the v2.0.0 surface.
- Comment example updated channels_v2/v1 → feed_v3/v2.
Client
- CLIENT_REQUIRED_FEATURES expanded to include the v2.0.0 feature
flags the client now depends on (feed_v2, media_scrub,
relay_broadcast); checkNodeVersion() will flag older nodes as
unsupported and surface an upgrade prompt.
All 7 Go test packages green; tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Excluded from release bundle:
- CONTEXT.md, CHANGELOG.md (agent/project working notes)
- client-app/ (React Native messenger — tracked separately)
- contracts/hello_go/ (unused standalone example)
Kept contracts/counter/ and contracts/name_registry/ as vm-test fixtures
(referenced by vm/vm_test.go; NOT production contracts).
Docs refactor:
- docs/README.md — new top-level index with cross-references
- docs/quickstart.md — rewrite around single-node as primary path
- docs/node/README.md — full rewrite, all CLI flags, schema table
- docs/api/README.md — add /api/well-known-version, /api/update-check
- docs/contracts/README.md — split native (Go) vs WASM (user-deployable)
- docs/update-system.md — new, full 5-layer update system design
- README.md — link into docs/, drop CHANGELOG/client-app references
Build-time version system (inherited from earlier commits this branch):
- node --version / client --version with ldflags-injected metadata
- /api/well-known-version with {build, protocol_version, features[]}
- Peer-version gossip on dchain/version/v1
- /api/update-check against Gitea release API
- deploy/single/update.sh with semver guard + 15-min systemd jitter