feat: custom profile avatars + profiles that reach share recipients#465
Merged
Conversation
added 3 commits
July 10, 2026 16:12
Custom avatar upload in Settings → Profile: the picture is center-cropped, downscaled and re-encoded client-side (stripping EXIF) into a small inline data URL stored on the Profile node, so it travels the same sync path as the display name. The shell's top-left profile button, profile menus, editor header and presence facepiles show the picture when set and fall back to the deterministic DID identicon otherwise. Profiles now reach shared contexts. A DID's canonical Profile lives at the deterministic profileNodeId(did) (profile-<did>, like inbox-<did>), so any collaborator can acquire an author's profile knowing only the createdBy DID. The hub treats profile rooms as hub-published identity: any authenticated DID may read them, only the subject DID may write (node-change + Yjs paths). Channel chat, threads, rosters, page comments, presence and the ShareDialog People tab acquire missing author profiles on sight, so share recipients see real names and pictures instead of DID fragments and identicons. Trust guards: dedupeProfiles prefers self-authored profiles so a write-role grantee can't override someone's identity by injecting a Profile node into a shared doc, and avatar sources are sanitized (data:image/https/blob only) before rendering, since profiles sync from untrusted peers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Contributor
|
Preview removed for PR #465. |
Contributor
🖼️ UI changes in this PRScreensInteractionsAuto-captured by CI · run. Informational — not a blocking check. |
…-profile-sharing-6e119f
crs48
added a commit
that referenced
this pull request
Jul 11, 2026
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @xnetjs/data@0.11.0 ### Minor Changes - [#465](#465) [`d9cd478`](d9cd478) Thanks [@crs48](https://github.com/crs48)! - Profiles gain a canonical deterministic node ID and room for inline avatar images: - New `profileNodeId(did)` / `didFromProfileNodeId(nodeId)` helpers — a DID's canonical Profile now lives at `profile-<did>` (same pattern as `inboxStateNodeId`), so any collaborator who knows a DID (e.g. from `createdBy` on shared content) can acquire the profile without a directory lookup. - `Profile.avatar` max length raised from 500 to 65536 so a small, client-side-downscaled `data:image/*` avatar can live inside the Profile node itself and reach share recipients through the same sync path as the display name. ### Patch Changes - Updated dependencies []: - @xnetjs/storage@0.11.0 - @xnetjs/sqlite@0.11.0 - @xnetjs/sync@0.11.0 - @xnetjs/identity@0.11.0 - @xnetjs/crypto@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/react@0.11.0 ### Minor Changes - [#467](#467) [`07b480d`](07b480d) Thanks [@crs48](https://github.com/crs48)! - Deliver a shared chat channel's nodes to a grantee (exploration 0298). `NodeStoreSyncProvider` gains a subscribe-only mode (receives + applies a room but never publishes local changes and never advances its cursor from a live broadcast — share rooms cursor on a per-room `seq`). `SyncManager` gains `subscribeShareRoom(room)` / `unsubscribeShareRoom(room)`, and a `channelShareRoom(id)` helper is exported (re-exported from `@xnetjs/react`). Together these let a client subscribe to a channel's `xnet-channel-<id>` share room so its node, message history, and members' profiles sync in — the transport that channel share links were missing. - [#470](#470) [`e68c016`](e68c016) Thanks [@crs48](https://github.com/crs48)! - Add `workspaceShareRoom(id)` (re-exported from `@xnetjs/react`) so a shared workspace (bench) node is delivered to a grantee via the same share-room mechanism as channels (exploration 0298 Phase 2). ### Patch Changes - Updated dependencies [[`07b480d`](07b480d), [`d9cd478`](d9cd478), [`e68c016`](e68c016)]: - @xnetjs/runtime@0.3.0 - @xnetjs/data@0.11.0 - @xnetjs/data-bridge@0.11.0 - @xnetjs/history@0.11.0 - @xnetjs/plugins@0.11.0 - @xnetjs/sync@0.11.0 - @xnetjs/identity@0.11.0 - @xnetjs/crypto@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/runtime@0.3.0 ### Minor Changes - [#467](#467) [`07b480d`](07b480d) Thanks [@crs48](https://github.com/crs48)! - Deliver a shared chat channel's nodes to a grantee (exploration 0298). `NodeStoreSyncProvider` gains a subscribe-only mode (receives + applies a room but never publishes local changes and never advances its cursor from a live broadcast — share rooms cursor on a per-room `seq`). `SyncManager` gains `subscribeShareRoom(room)` / `unsubscribeShareRoom(room)`, and a `channelShareRoom(id)` helper is exported (re-exported from `@xnetjs/react`). Together these let a client subscribe to a channel's `xnet-channel-<id>` share room so its node, message history, and members' profiles sync in — the transport that channel share links were missing. - [#470](#470) [`e68c016`](e68c016) Thanks [@crs48](https://github.com/crs48)! - Add `workspaceShareRoom(id)` (re-exported from `@xnetjs/react`) so a shared workspace (bench) node is delivered to a grantee via the same share-room mechanism as channels (exploration 0298 Phase 2). ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/data-bridge@0.11.0 - @xnetjs/history@0.11.0 - @xnetjs/plugins@0.11.0 - @xnetjs/storage@0.11.0 - @xnetjs/sync@0.11.0 - @xnetjs/identity@0.11.0 - @xnetjs/crypto@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/abuse@0.11.0 ### Patch Changes - Updated dependencies []: - @xnetjs/identity@0.11.0 - @xnetjs/crypto@0.11.0 ## @xnetjs/cli@0.1.3 ### Patch Changes - Updated dependencies [[`07b480d`](07b480d), [`d9cd478`](d9cd478), [`e68c016`](e68c016)]: - @xnetjs/runtime@0.3.0 - @xnetjs/data@0.11.0 - @xnetjs/plugins@0.11.0 - @xnetjs/sqlite@0.11.0 - @xnetjs/sync@0.11.0 - @xnetjs/identity@0.11.0 - @xnetjs/crypto@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/crypto@0.11.0 ### Patch Changes - Updated dependencies []: - @xnetjs/core@0.11.0 ## @xnetjs/data-bridge@0.11.0 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/sqlite@0.11.0 - @xnetjs/sync@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/history@0.11.0 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/sync@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/identity@0.11.0 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/plugins@0.11.0 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/abuse@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/storage@0.11.0 ### Patch Changes - Updated dependencies []: - @xnetjs/sqlite@0.11.0 - @xnetjs/crypto@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/sync@0.11.0 ### Patch Changes - Updated dependencies []: - @xnetjs/identity@0.11.0 - @xnetjs/crypto@0.11.0 - @xnetjs/core@0.11.0 ## @xnetjs/core@0.11.0 ## @xnetjs/sqlite@0.11.0 ## xnet-cloud@0.0.14 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.11.0 - @xnetjs/cloud@0.0.1 ## @xnetjs/brain@0.0.15 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/vectors@0.0.1 ## @xnetjs/comms@0.0.15 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/crypto@0.11.0 ## @xnetjs/dashboard@0.0.15 ### Patch Changes - Updated dependencies [[`07b480d`](07b480d), [`d9cd478`](d9cd478), [`e68c016`](e68c016)]: - @xnetjs/react@0.11.0 - @xnetjs/data@0.11.0 - @xnetjs/plugins@0.11.0 - @xnetjs/social@0.0.15 ## @xnetjs/labs@0.0.15 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/plugins@0.11.0 ## @xnetjs/licenses@0.0.15 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.11.0 ## @xnetjs/maps@0.0.15 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 ## @xnetjs/meetings@0.0.8 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/plugins@0.11.0 ## @xnetjs/server@0.0.14 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/data-bridge@0.11.0 - @xnetjs/identity@0.11.0 - @xnetjs/crypto@0.11.0 ## @xnetjs/social@0.0.15 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 - @xnetjs/crypto@0.11.0 ## @xnetjs/unreal@0.0.15 ### Patch Changes - Updated dependencies [[`d9cd478`](d9cd478)]: - @xnetjs/data@0.11.0 ## xnet-desktop@0.11.0 Desktop shell release riding the @xnetjs/core 0.11.0 train. Desktop-specific changes are not tracked here; see the core packages' changelogs for what shipped.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.





What
Custom avatars. Settings → Profile gains a Picture field: uploads are center-cropped, downscaled to 192px, and re-encoded through a canvas (which strips EXIF/GPS metadata) into a small inline `data:image/*` URL stored on the Profile node — one sync path for name and picture, no blob authorization needed across share boundaries. The top-left profile button, profile menu/sheet, mobile shell, and editor header show the picture when set; without one they show the deterministic DID identicon (the "default genmoji"), via a shared `SelfAvatar` component. The profile menu now shows your display name instead of "You".
Profiles in shared contexts. Share recipients used to see `did:key…` fragments and identicons for every author, because Profile nodes never reached them (the "hub-published" intent in `auth-exempt.ts` was never implemented). Now:
Security reflection
Sharing something with someone implies they can see who you are — so profiles (display name, handle, avatar, status) are readable by any authenticated DID on the hub, matching the "public identity" intent already documented in `auth-exempt.ts`. Per-share profile ACLs can't work anyway: a recipient needs the profiles of all authors in a doc, not just the sharer. Guard rails added:
Tests / verification
Changeset: `@xnetjs/data` minor (new `profileNodeId`/`didFromProfileNodeId` exports; `avatar` maxLength 500 → 65536, a loosening). Hub/editor/app are private packages.
🤖 Generated with Claude Code