Commit Graph

3 Commits

Author SHA1 Message Date
vsecoder
ce11a13874 feat: desktop messaging + pairing + cross-client master-pub attribution (v2.2.0-alpha5)
Two coordinated changes:

1. Desktop client gets a functional Messages section and working pairing
flow, putting it at feature parity with mobile for the v2.2.0 line.

2. Server + both clients teach each other to use the sender's master
Ed25519 (not just their X25519) to address conversations, so a peer
writing from a different linked device still rolls into the same chat.
This is the "new API logic" the desktop scaffold was waiting on.

Server (node/api_relay.go, cmd/node/main.go):
  * /relay/inbox items now carry `sender_ed25519_pub` alongside the
    per-device `sender_pub`. Empty string for pre-v2.2.0 senders.
  * WS `inbox` push summary also includes `sender_ed25519_pub`, so the
    client can skip the refetch when the envelope plainly isn't for
    the chat they're watching.
  * Both existing tests pass.

Mobile client:
  * lib/types.ts Envelope grew `sender_ed25519_pub`; fetchInbox normalises
    it (default '') for older nodes.
  * hooks/useGlobalInbox matches contacts by (master Ed25519 OR legacy
    X25519) so an incoming message from a peer's desktop reuses the
    existing chat instead of creating a duplicate placeholder.
  * hooks/useMessages now takes an optional `contactMasterEd25519` and
    exposes a matchesChat() predicate; WS inbox handler uses it too to
    avoid spurious refetches.
  * chats/[id].tsx passes `contact.address` (master) along with x25519.

Desktop client — all new:
  * src/lib/crypto.ts — tweetnacl hex/base64 helpers, generateKeyFile,
    encryptMessage/decryptMessage, signBase64, shortAddr. Same signatures
    as the mobile lib; uses Chromium's window.crypto, no expo-crypto dep.
  * src/lib/tx.ts — buildTransferTx / buildLinkDeviceTx / buildUnlinkDeviceTx
    + submitTx + humanizeTxError, canonical-bytes identical to mobile.
  * src/lib/relay.ts — fetchInbox, sendEnvelope, resolveRecipientKeys
    (multi-device fan-out with legacy identity.x25519 fallback).
  * src/lib/store.ts — zustand state gets messages{}, unread{},
    activeChat.
  * src/lib/storage.ts — per-chat cache via localStorage (500-msg cap).
  * src/hooks/useInboxPoll — 4s polling loop, addresses conversations
    by master Ed25519, bumps unread unless chat is active.
  * src/sections/messages/* — ChatList (sorted tiles, unread badges),
    Conversation (auto-scroll messages + composer + fan-out send,
    Enter-to-send / Shift+Enter for newline), EmptyConversation.
  * src/auth/Pair.tsx — 6-digit code + device key screen, polls inbox
    for a handshake envelope, assembles the KeyFile on arrival.
  * Welcome.tsx: Pair button now actually routes to <Pair>; imports
    generateKeyFile from lib/crypto (was inlined).

docs/ROADMAP.md delta: alpha5 row flipped to done inline. Alpha6
(feed + wallet) and rc1 (contacts + devices UI + profile) still
pending.
2026-04-22 17:43:18 +03:00
vsecoder
0bb5780a5d feat(feed/chat): VK-style share post to chats + list breathing room
Feed list padding
  FlatList had no inner padding so the first post bumped against the
  tab strip and the last post against the NavBar. Added paddingTop: 8
  / paddingBottom: 24 on contentContainerStyle in both /feed and
  /feed/tag/[tag] — first card now has a clear top gap, last card
  doesn't get hidden behind the FAB or NavBar.

Share-to-chat flow
  Replaces the placeholder share button (which showed an Alert with
  the post URL) with a real "forward to chats" flow modeled on VK's
  shared-wall-post embed.

  New modules
    lib/forwardPost.ts       — encodePostRef / tryParsePostRef +
                               forwardPostToContacts(). Serialises a
                               feed post into a tiny JSON payload that
                               rides the same encrypted envelope as any
                               chat message; decode side distinguishes
                               "post_ref" payloads from regular text by
                               trying JSON.parse on decrypted text.
                               Mirrors the sent message into the sender's
                               local history so they see "you shared
                               this" in the chat they forwarded to.

    components/feed/ShareSheet.tsx
                             — bottom-sheet picker. Multi-select
                               contacts via tick-box, search by
                               username / alias / address prefix.
                               "Send (N)" dispatches N parallel
                               encrypted envelopes. Contacts with no
                               X25519 key are filtered out (can't
                               encrypt for them).

    components/chat/PostRefCard.tsx
                             — compact embedded-post card for chat
                               bubbles. Ribbon "ПОСТ" label +
                               author + 3-line excerpt + "с фото"
                               indicator. Tap → /(app)/feed/{id} full
                               post detail. Palette switches between
                               blue-bubble-friendly and peer-bubble-
                               friendly depending on bubble side.

  Message pipeline
    lib/types.ts           — Message.postRef optional field added.
                             text stays "" when the message is a
                             post-ref (nothing to render as plain text).
    hooks/useMessages.ts   + hooks/useGlobalInbox.ts
                           — post decryption of every inbound envelope
                             runs through tryParsePostRef; matching
                             messages get the postRef attached instead
                             of the raw JSON in .text.
    components/chat/MessageBubble.tsx
                           — renders PostRefCard inside the bubble when
                             msg.postRef is set. Other bubble features
                             (reply quote, attachment preview, text)
                             still work around it.

  PostCard
    - share icon now opens <ShareSheet>; the full-URL placeholder is
      gone. ShareSheet is embedded at the PostCard level so each card
      owns its own sheet state (avoids modal-stacking issues).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:26:43 +03:00
vsecoder
5b64ef2560 feat(client): Twitter-style social feed UI (Phase C of v2.0.0)
Ships the client side of the v2.0.0 feed feature. Folds client-app/
into the monorepo (was previously .gitignored as "tracked separately"
but no separate repo ever existed — for v2.0.0 the client is
first-class).

Feed screens

  app/(app)/feed.tsx — Feed tab
    - Three-way tab strip: Подписки / Для вас / В тренде backed by
      /feed/timeline, /feed/foryou, /feed/trending respectively
    - Default landing tab is "Для вас" — surfaces discovery without
      requiring the user to follow anyone first
    - FlatList with pull-to-refresh + viewability-driven view counter
      bump (posts visible ≥ 60% for ≥ 1s trigger POST /feed/post/…/view)
    - Floating blue compose button → /compose
    - Per-post liked_by_me fetched in batches of 6 after list load

  app/(app)/compose.tsx — post composer modal
    - Fullscreen, Twitter-like header (✕ left, Опубликовать right)
    - Auto-focused multiline TextInput, 4000 char cap
    - Hashtag preview chips that auto-update as you type
    - expo-image-picker + expo-image-manipulator pipeline: resize to
      1080px max-dim, JPEG Q=50 (client-side first-pass compression
      before the mandatory server-side scrub)
    - Live fee estimate + balance guard with a confirmation modal
      ("Опубликовать пост? Цена: 0.00X T · Размер: N KB")
    - Exif: false passed to ImagePicker as an extra privacy layer

  app/(app)/feed/[id].tsx — post detail
    - Full PostCard rendering + detailed info panel (views, likes,
      size, fee, hosting relay, hashtags as tappable chips)
    - Triggers bumpView on mount
    - 410 (on-chain soft-delete) routes back to the feed

  app/(app)/feed/tag/[tag].tsx — hashtag feed

  app/(app)/profile/[address].tsx — rebuilt
    - Twitter-ish profile: avatar, name, address short-form, post count
    - Posts | Инфо tab strip
    - Follow / Unfollow button for non-self profiles (optimistic UI)
    - Edit button on self profile → settings
    - Secondary actions (chat, copy address) when viewing a known contact

Supporting library

  lib/feed.ts — HTTP wrappers + tx builders for every /feed/* endpoint:
    - publishPost (POST /feed/publish, signed)
    - publishAndCommit (publish → on-chain CREATE_POST)
    - fetchPost / fetchStats / bumpView
    - fetchAuthorPosts / fetchTimeline / fetchForYou / fetchTrending /
      fetchHashtag
    - buildCreatePostTx / buildDeletePostTx
    - buildFollowTx / buildUnfollowTx
    - buildLikePostTx / buildUnlikePostTx
    - likePost / unlikePost / followUser / unfollowUser / deletePost
      (high-level helpers that bundle build + submitTx)
    - formatFee, formatRelativeTime, formatCount — Twitter-like display
      helpers

  components/feed/PostCard.tsx — core card component
    - Memoised for performance (N-row re-render on every like elsewhere
      would cost a lot otherwise)
    - Optimistic like toggle with heart-bounce spring animation
    - Hashtag highlighting in body text (tappable → hashtag feed)
    - Long-press context menu (Delete, owner-only)
    - Views / likes / share-link / reply icons in footer row

Navigation cleanup

  - NavBar: removed the SOON pill on the Feed tab (it's shipped now)
  - (app)/_layout: hide NavBar on /compose and /feed/* sub-routes
  - AnimatedSlot: treat /feed/<id>, /feed/tag/<t>, /compose as
    sub-routes so back-swipe-right closes them

Channel removal (client side)

  - lib/types.ts: ContactKind stripped to 'direct' | 'group'; legacy
    'channel' flag removed. `kind` field kept for backward compat with
    existing AsyncStorage records.
  - lib/devSeed.ts: dropped the 5 channel seed contacts.
  - components/ChatTile.tsx: removed channel kindIcon branch.

Dependencies

  - expo-image-manipulator added for client-side image compression.
  - expo-file-system/legacy used for readAsStringAsync (SDK 54 moved
    that API to the legacy sub-path; the new streaming API isn't yet
    stable).

Type check

  - npx tsc --noEmit — clean, 0 errors.

Next (not in this commit)

  - Direct attachment-bytes endpoint on the server so post-detail can
    actually render the image (currently shows placeholder with URL)
  - Cross-relay body fetch via /api/relays + hosting_relay pubkey
  - Mentions (@username) with notifications
  - Full-text search

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:43:55 +03:00