perf(client): narrow selectors + virtualization; frontend cleanup (#87, #91) #108

Closed
icub3d wants to merge 3 commits from feature/87-91-frontend into main
Owner

Frontend performance and cleanup work from the 2026-07-07 code review, in one PR.

Closes #87. Closes #91.

#87 — whole-store subscriptions + no virtualization

  • AppShell / App: replace whole-store Zustand subscriptions with useShallow slices, so the chat UI no longer reconciles on every incoming WS event.
  • MessageItem: wrapped in React.memo; the thread-fetch effect now depends on primitive thread_id / reply_count instead of message.thread object identity, so it no longer re-fetches on unrelated message updates.
  • MessageList / MemberList: windowed with @tanstack/react-virtual (new dependency).
  • MemberList: member sort memoized with useMemo.
  • serverStore: MESSAGE_CREATE now binary-search inserts into the already-sorted array instead of re-sorting the whole thing per event.

#91 — cleanup + minor security

  • WS token out of the URL: the session token was a ?token= query param, landing in access/proxy logs and history. Now:
    • server accepts Authorization: Bearer (non-browser clients) or an access_token.<token> WebSocket subprotocol (browsers, which can't set WS headers); it echoes decentcom.v1 back so the handshake never reflects the token; the query-param path is removed entirely. Unit tests added for token extraction.
    • Rust SDK sends the bearer header on the handshake request.
    • Web client offers ["decentcom.v1", "access_token.<token>"] subprotocols.
  • Deduped helpers: shared utils/pubkey.ts (truncatePubkey/shortPubkey) and utils/address.ts (normalizeAddress) replace the copy-pasted local copies in MessageItem/MemberList/Setup/appStore/serverStore.
  • serverStore: extracted loadServerData() shared by connect() / revalidate().
  • api: extracted extractErrorMessage(), reused by apiRequest and the media upload path.
  • KeySafety/Setup: seed/identity state cleared on completion; clipboard best-effort cleared after copy.

Also

  • Dropped a stray npm package-lock.json (repo uses pnpm); @tanstack/react-virtual is tracked in pnpm-lock.yaml.
  • One-line fix for an unused serde::Serialize import in server/src/profiles/handlers.rs left by #105 — it was breaking cargo clippy -- -D warnings on main.

Verification

  • pnpm lint, pnpm test (89 passed), pnpm build — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace — 202 passed

🤖 Generated with Claude Code

Frontend performance and cleanup work from the 2026-07-07 code review, in one PR. Closes #87. Closes #91. ## #87 — whole-store subscriptions + no virtualization - **AppShell / App**: replace whole-store Zustand subscriptions with `useShallow` slices, so the chat UI no longer reconciles on every incoming WS event. - **MessageItem**: wrapped in `React.memo`; the thread-fetch effect now depends on primitive `thread_id` / `reply_count` instead of `message.thread` object identity, so it no longer re-fetches on unrelated message updates. - **MessageList / MemberList**: windowed with `@tanstack/react-virtual` (new dependency). - **MemberList**: member sort memoized with `useMemo`. - **serverStore**: `MESSAGE_CREATE` now binary-search inserts into the already-sorted array instead of re-sorting the whole thing per event. ## #91 — cleanup + minor security - **WS token out of the URL**: the session token was a `?token=` query param, landing in access/proxy logs and history. Now: - server accepts `Authorization: Bearer` (non-browser clients) or an `access_token.<token>` WebSocket subprotocol (browsers, which can't set WS headers); it echoes `decentcom.v1` back so the handshake never reflects the token; the query-param path is removed entirely. Unit tests added for token extraction. - Rust SDK sends the bearer header on the handshake request. - Web client offers `["decentcom.v1", "access_token.<token>"]` subprotocols. - **Deduped helpers**: shared `utils/pubkey.ts` (`truncatePubkey`/`shortPubkey`) and `utils/address.ts` (`normalizeAddress`) replace the copy-pasted local copies in MessageItem/MemberList/Setup/appStore/serverStore. - **serverStore**: extracted `loadServerData()` shared by `connect()` / `revalidate()`. - **api**: extracted `extractErrorMessage()`, reused by `apiRequest` and the media upload path. - **KeySafety/Setup**: seed/identity state cleared on completion; clipboard best-effort cleared after copy. ## Also - Dropped a stray npm `package-lock.json` (repo uses pnpm); `@tanstack/react-virtual` is tracked in `pnpm-lock.yaml`. - One-line fix for an unused `serde::Serialize` import in `server/src/profiles/handlers.rs` left by #105 — it was breaking `cargo clippy -- -D warnings` on main. ## Verification - `pnpm lint`, `pnpm test` (89 passed), `pnpm build` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo test --workspace` — 202 passed 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The gateway session token was passed as a WebSocket URL query param
(`?token=…`), so it landed in access/proxy logs and browser history. Move it
out of the URL on every client:

- Server (`gateway/handler.rs`): accept the token from the `Authorization:
  Bearer` header (non-browser clients) or, for browsers — which can't set WS
  headers but can offer subprotocols — from an `access_token.<token>`
  WebSocket subprotocol. The server echoes back the `decentcom.v1` subprotocol
  so the browser handshake completes cleanly and never reflects the token. The
  URL query path is removed entirely. Added unit tests for token extraction.
- Rust SDK (`gateway.rs`): send the token in an `Authorization: Bearer` header
  on the handshake request instead of the URL; `build_ws_url` no longer takes
  or appends a token.
- Web client (`services/gateway.ts`): pass `["decentcom.v1",
  "access_token.<token>"]` as the WebSocket subprotocols instead of a URL
  query param.

Session tokens are hex, so they are valid WebSocket subprotocol tokens.

Part of #91

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(server): drop unused serde::Serialize import left by #105
Some checks failed
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 6s
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 16s
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 24s
CI / Client — test & lint (pull_request) Successful in 46s
Security Audit / Semgrep (SAST) (pull_request) Failing after 1m11s
CI / Server — test & lint (pull_request) Successful in 2m50s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Failing after 3m19s
83c7e788d2
Pre-existing on main; blocks `cargo clippy -- -D warnings`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

Superseded by #107, which merged the same underlying commits (e305ed3, b809ffc) to main; the lockfile and clippy fixes here are also already present on main. Closing.

Superseded by #107, which merged the same underlying commits (e305ed3, b809ffc) to main; the lockfile and clippy fixes here are also already present on main. Closing.
icub3d closed this pull request 2026-07-11 03:26:55 +00:00
icub3d deleted branch feature/87-91-frontend 2026-07-11 03:28:00 +00:00
Some checks failed
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 6s
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 16s
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 24s
CI / Client — test & lint (pull_request) Successful in 46s
Security Audit / Semgrep (SAST) (pull_request) Failing after 1m11s
CI / Server — test & lint (pull_request) Successful in 2m50s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Failing after 3m19s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
icub3d/decentcom!108
No description provided.