Every user has a profile page at /{user}/EditProfile where they change how they appear to everyone else: their picture, their display name, their language and time zone, a short bio, their links and the showcase of pinned work. Visitors see the read-only result at /{user}/Profile.

It is reached from three places:

The page is owner-only: it requires Update on the user node, so a visitor who opens the URL gets the access-denied view.


What is on the page

Section What it edits Where the value lives
Picture Upload, replace or remove the profile picture the user node's Icon (content:picture/…), bytes in the node's own content collection
Basics Display name · sign-in email (read-only) · language · time zone MeshNode.Name · User.Email · User.Locale · User.TimeZoneId
Bio A short markdown bio User.Bio
Links One markdown link per line User.Links
Showcase Pinned cards, unpinned in place User.PinnedPaths
contributed Whatever a module adds — e.g. the Store's subscription the module's own nodes

Every field is bound directly to the user node (Data Binding): there is no /data copy, no save subscription and no Save button — a change is written through GetMeshNodeStream(path).Update(...) as it happens. The page itself only re-renders when its shape changes (the node appears, or the pins change), so a field being typed in is never rebuilt under the cursor.

The display name is the node's own Name, which is what the header, mentions, cards and the public profile show. It is written when the field loses focus, not per keystroke. The language and time zone pickers are the same fields and the same control as Settings → Preferences, so the two surfaces cannot disagree.


The picture

The picture is a NodeImageUploadControl(nodePath) — a platform control, usable for any node whose icon is an image. Its view reads the picture from the node stream and writes through NodeImageUpload (MeshWeaver.ContentCollections):

content: references resolve to the access-controlled /api/content/{nodePath}/… URL. MeshNodeImageHelper.ResolvePictureUrl(icon, nodePath) returns that URL for a picture and null for an emoji, a glyph name or any SVG (inline, data:image/svg…, or a .svg path), so an avatar falls back to initials instead of a broken image. The header avatar, the public profile and node cards and mentions all use the node's icon, so the picture appears everywhere at once.


Adding a section from a module

A module adds a section without core referencing the module, the same way it adds a settings tab (Settings Page): a provider carried on the user hub's configuration, contributed onto the User node type with AddNodeHubContribution.

// In the module's own configuration (e.g. its ConfigureDefaultNodeHub).
config.AddNodeHubContribution(UserNodeType.NodeType, userHub => userHub
    .AddProfileSections(new ProfileSectionDefinition(
        Id: "subscription",
        Title: "Subscription",
        ContentBuilder: (host, userNode) => BuildSubscription(host, userNode),
        Order: 100)
    { TitleKey = "store.profileSubscription" }));
Type Namespace Role
ProfileSectionDefinition(Id, Title, ContentBuilder, Order = 0, RequiredPermission = Update, Icon = null) { TitleKey } MeshWeaver.Mesh One section
ProfileSectionBuilder(LayoutAreaHost host, MeshNode? userNode) → UiControl MeshWeaver.Mesh Builds the section body; the page supplies the heading
ProfileSectionProvider(LayoutAreaHost host, RenderingContext ctx) → IObservable<IReadOnlyList<ProfileSectionDefinition>> MeshWeaver.Mesh A reactive provider, for sections that depend on a live check
AddProfileSections(params ProfileSectionDefinition[]) / AddProfileSections(params ProfileSectionProvider[]) MeshWeaver.Graph.Configuration Registration on the user hub

The rules the page applies:


Source