@@ -81,6 +81,69 @@ if (canFetchAuthorAvatar) {
8181 // descriptor before turning avatar.body into a Blob URL for one <img>.
8282 }
8383}` ,
84+ avatarFeatureDetection : `const actions = await window.qdnRequest({
85+ action: 'SHOW_ACTIONS',
86+ });
87+
88+ const canFetchAccountAvatar = actions.includes('FETCH_ACCOUNT_AVATAR');
89+ const canFetchGroupAvatar = actions.includes('FETCH_GROUP_AVATAR');
90+ const canSetAccountAvatar = actions.includes('SET_ACCOUNT_AVATAR');
91+ const canSetGroupAvatar = actions.includes('SET_GROUP_AVATAR');` ,
92+ fetchAvatar : `const MAX_AVATAR_BYTES = 500 * 1024;
93+
94+ async function loadVisibleAccountAvatar(address) {
95+ const result = await window.qdnRequest({
96+ action: 'FETCH_ACCOUNT_AVATAR',
97+ address,
98+ maxBytes: MAX_AVATAR_BYTES,
99+ });
100+
101+ if (result.status === 'PENDING') {
102+ window.setTimeout(() => loadVisibleAccountAvatar(address),
103+ (result.retryAfterSeconds ?? 5) * 1000);
104+ return null;
105+ }
106+
107+ if (result.encoding !== 'base64' ||
108+ result.contentLength > MAX_AVATAR_BYTES ||
109+ !String(result.contentType).startsWith('image/')) return null;
110+
111+ const binary = atob(result.body);
112+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
113+ if (bytes.byteLength !== result.contentLength) return null;
114+
115+ // source is 'POINTER' or 'LEGACY'. descriptor is the pointer tuple when set.
116+ const url = URL.createObjectURL(new Blob([bytes], { type: result.contentType }));
117+ return { source: result.source, descriptor: result.descriptor, url };
118+ }
119+
120+ // Revoke the prior URL when its image is replaced or unmounted.
121+ // Use FETCH_GROUP_AVATAR with { groupId } for a visible group avatar.` ,
122+ setAvatarPointer : `// First publish a public, single-file image resource and wait for READY.
123+ // Publishing does not set the avatar pointer by itself.
124+ const avatar = {
125+ service: 'THUMBNAIL',
126+ name: selectedPrimaryName,
127+ identifier: 'avatar',
128+ };
129+
130+ await window.qdnRequest({
131+ action: 'SET_ACCOUNT_AVATAR',
132+ avatar,
133+ });
134+
135+ // A group setter also requires its group id:
136+ await window.qdnRequest({
137+ action: 'SET_GROUP_AVATAR',
138+ groupId,
139+ avatar: {
140+ service: 'THUMBNAIL',
141+ name: groupOwnerPrimaryName,
142+ identifier: \`qortium-group-avatar-v1-\${groupId}\`,
143+ },
144+ });
145+
146+ // Clear either pointer with a separate approved request: { avatar: null }.` ,
84147 notifications : `const postId = 'm1abc123';
85148
86149await window.qdnRequest({
@@ -252,6 +315,7 @@ export default function Reference() {
252315 < a href = "#lifecycle" > Lifecycle</ a >
253316 < a href = "#metadata" > Metadata</ a >
254317 < a href = "#bridge" > Home bridge</ a >
318+ < a href = "#avatars" > Avatars</ a >
255319 < a href = "#examples" > Examples</ a >
256320 </ nav >
257321
@@ -513,9 +577,70 @@ export default function Reference() {
513577 </ aside >
514578 </ section >
515579
580+ < section className = "reference-section" id = "avatars" >
581+ < div className = "reference-section__heading" >
582+ < p className = "reference-kicker" > 06 · Account and group avatars</ p >
583+ < h2 > Use the pointer-aware bridge, not a named thumbnail URL</ h2 >
584+ < p >
585+ Call < code > SHOW_ACTIONS</ code > before showing avatar controls. Fetch images only for identities currently
586+ visible in the interface; do not turn a batch identity lookup into a batch image download.
587+ </ p >
588+ </ div >
589+
590+ < CopyableCode label = "Avatar capability detection" snippet = "avatarFeatureDetection" />
591+
592+ < div className = "reference-grid" >
593+ < ReferenceCard title = "Safe reads" >
594+ < p >
595+ < code > FETCH_ACCOUNT_AVATAR</ code > accepts an < code > address</ code > (or the selected account), and
596+ < code > FETCH_GROUP_AVATAR</ code > accepts a positive < code > groupId</ code > or < code > txGroupId</ code > .
597+ These reads are public-node safe.
598+ </ p >
599+ < p >
600+ A ready result carries base64 < code > body</ code > , < code > contentType</ code > , < code > contentLength</ code > ,
601+ < code > source</ code > , and an optional < code > { '{ service, name, identifier }' } </ code > descriptor. Build an
602+ in-memory Blob URL only; never rebuild a raw node/QDN URL from the response.
603+ </ p >
604+ </ ReferenceCard >
605+ < ReferenceCard title = "Pending and fallback" >
606+ < p >
607+ A < code > status: 'PENDING'</ code > result is retryable after < code > retryAfterSeconds</ code > . Keep initials
608+ visible while it is queued. Missing, malformed, or unsupported results fall back to initials without a
609+ retry loop.
610+ </ p >
611+ < p >
612+ An explicit pointer wins and resolves to its latest resource revision. Invalid pointer content fails
613+ closed. A < code > source: 'LEGACY'</ code > result is compatibility data, not an on-chain pointer; do not use
614+ < code > avatarSrc</ code > or < code > avatarUrl</ code > as an authoritative image source.
615+ </ p >
616+ </ ReferenceCard >
617+ < ReferenceCard title = "Authoring" >
618+ < p >
619+ Publish the public single-file image first, wait for that resource to become < code > READY</ code > , then
620+ call the matching setter. The publish and pointer assignment are separate, single-request approvals.
621+ </ p >
622+ < p >
623+ Both setters accept < code > { '{ service, name, identifier }' } </ code > or < code > avatar: null</ code > to clear.
624+ Account assignment targets the selected account; group assignment also includes < code > groupId</ code > .
625+ </ p >
626+ </ ReferenceCard >
627+ </ div >
628+
629+ < CopyableCode label = "Fetch one visible account avatar" snippet = "fetchAvatar" />
630+ < CopyableCode label = "Set or clear an avatar pointer" snippet = "setAvatarPointer" />
631+
632+ < aside className = "reference-callout" >
633+ < strong > Bounded, mutable image data.</ strong >
634+ < p >
635+ Home validates raster image bytes and caps avatar responses at 500 KiB. A pointer is intentionally mutable,
636+ so cache it briefly and revalidate rather than treating a descriptor as an immutable signature.
637+ </ p >
638+ </ aside >
639+ </ section >
640+
516641 < section className = "reference-section" id = "examples" >
517642 < div className = "reference-section__heading" >
518- < p className = "reference-kicker" > 06 · Copyable examples</ p >
643+ < p className = "reference-kicker" > 07 · Copyable examples</ p >
519644 < h2 > Publish, discover, fetch, and delete</ h2 >
520645 < p >
521646 These examples use the Qortium Home bridge. Substitute real selected-account names and generated short
0 commit comments