Developer Docs

ActorBadge Component System

The ActorBadge is Mallnline's primary identity surface โ€” a context-aware component that renders user attribution with sigils, affiliation badges, and an interactive hover popover. It is the visual manifestation of the Sigil Taxonomy and the frontend counterpart of the User Identity Resolution pipeline.

Every place a user identity appears in the UI โ€” community Q&A threads, reviews, uChat participants, team listings โ€” uses ActorBadge to resolve raw userId values into rich, interactive identity cards.

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                  ActorBadge                      โ”‚
โ”‚                                                  โ”‚
โ”‚  Props: userId, profiles, authorAssociation,     โ”‚
โ”‚         maletHandle, isPrivate                   โ”‚
โ”‚                                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚ Identity Resolution (5-tier fallback)     โ”‚    โ”‚
โ”‚  โ”‚  1. profile.handle     โ†’ r|mdenzo         โ”‚    โ”‚
โ”‚  โ”‚  2. profile.displayName โ†’ Meek Denzo      โ”‚    โ”‚
โ”‚  โ”‚  3. Affiliation context โ†’ ๐Ÿช Ownerยทm|...  โ”‚    โ”‚
โ”‚  โ”‚  4. Auto-fetched affil  โ†’ (same)          โ”‚    โ”‚
โ”‚  โ”‚  5. Anonymous fallback  โ†’ Mallnline User  โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚ Sigil Engine                              โ”‚    โ”‚
โ”‚  โ”‚  v| โ†’ Visitor (default)                   โ”‚    โ”‚
โ”‚  โ”‚  r| โ†’ Representative (owner/staff)        โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚ Hover Popover (context-aware)             โ”‚    โ”‚
โ”‚  โ”‚  Self:  Edit Profile                      โ”‚    โ”‚
โ”‚  โ”‚  Other: Follow + uChat                    โ”‚    โ”‚
โ”‚  โ”‚  Anon:  Sign in                           โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Usage

Basic (community thread)

<ActorBadge userId={comment.createdBy} {profiles} />

With affiliation context (Malet Owner reply)

<ActorBadge
  userId={review.ownerResponse.authorId}
  {profiles}
  authorAssociation="OWNER"
  maletHandle="luminara-crafts"
/>

Anonymous/private

<ActorBadge userId={userId} {profiles} isPrivate={true} />

Sigil Taxonomy

The sigil prefix dynamically switches based on affiliation context:

Sigil Context Trigger
v| Visitor (default) No authorAssociation or resolvedAffiliation
r| Representative authorAssociation is OWNER, MEMBER, or COLLABORATOR โ€” OR resolvedAffiliation is set

The sigil applies to both the inline badge and the popover handle display.

NOTE

The r| sigil represents a user acting in an official capacity for a Malet. It uses the user's own handle (e.g., r|mdenzo), never the Malet handle. The Malet affiliation is shown separately via the affiliation badge.

Identity Resolution

When rendering a user, ActorBadge follows a strict 5-tier fallback:

  1. Nodes profile handle โ†’ r|mdenzo or v|mdenzo
  2. Nodes profile displayName โ†’ Meek Denzo
  3. Inline affiliation context โ†’ ๐Ÿช Owner ยท m|luminara-crafts (when maletHandle + authorAssociation are passed but profile resolution fails)
  4. Auto-fetched affiliation โ†’ Same as above, but resolved via eager userAffiliations query on mount
  5. Anonymous fallback โ†’ Mallnline User with MU initials

IMPORTANT

Raw user IDs are never exposed in the UI. The previous fallback userId.slice(0, 8) + 'โ€ฆ' was replaced with 'Mallnline User' in profileResolver.ts. This is enforced in both formatUserDisplay() and getAvatarInitials().

Affiliation Badges

When a user has an association with a Malet (owner, staff), the badge renders:

  • Inline association label โ€” Owner, Member, Collaborator, Contributor, First Timer
  • Affiliation badge (in popover) โ€” ๐Ÿช Owner ยท m|luminara-crafts

Affiliations are resolved via two paths:

  • Explicit: Parent passes authorAssociation + maletHandle props
  • Auto-resolved: On mount, if the profile is unresolved and no explicit context exists, ActorBadge eagerly queries userAffiliations(userId)

Self-Aware Popover

The hover popover adapts based on whether the logged-in user is viewing their own badge:

Context Actions shown
Self โœ๏ธ Edit Profile โ†’ /settings/profile
Authenticated (other) Follow / Following โœ“ + ๐Ÿ’ฌ uChat โ†’ /uchat?dm={handle}
Unauthenticated Sign in โ†’ /auth

The popover mini-profile (avatar + display name + handle) is clickable, linking to /u/{handle}.

TIP

The self-detection uses $currentUser?.id === userId derived from the currentUser Svelte store. This also skips the isFollowing API call for the user's own badge, saving a network request per badge instance.

uChat Integration

The popover's uChat button routes to /uchat?dm={handle} using the resolved profile handle โ€” never the raw UUID. The button only renders when:

  1. The user is viewing someone else's badge (not self)
  2. The profile handle is resolved
  3. The user is authenticated

Error Sanitization

The uChat store uses a shared sanitizeGqlError() helper to prevent raw GraphQL response dumps:

function sanitizeGqlError(err: any, fallback: string): string {
  const gqlMessage = err?.response?.errors?.[0]?.message;
  if (gqlMessage?.includes('Authentication required')) {
    return 'Please sign in to access uChat.';
  }
  if (gqlMessage) return gqlMessage;
  if (err instanceof Error) return err.message;
  return fallback;
}

This is applied to loadConversations, openConversation, and createConversation.

Key Files

File Purpose
src/lib/components/ActorBadge.svelte Main component
src/lib/utils/profileResolver.ts formatUserDisplay(), getAvatarInitials(), resolveUserProfiles()
src/lib/queries/malet.ts USER_AFFILIATIONS query
src/lib/queries/follows.ts IS_FOLLOWING, FOLLOW_ENTITY, UNFOLLOW_ENTITY
src/lib/uchat/store.svelte.ts sanitizeGqlError() and conversation management
src/stores/auth.ts currentUser store for self-detection