{"id":"88c4fc76-e230-40a1-974e-d42da83d9e20","task":"Add rich-text facets to a Bluesky post so URLs render as links, @handles render as mentions (and notify the user), and #tags render as hashtags.","domain":"bsky.app","steps":["Compose the raw post text first, e.g. '✨ example mentioning @atproto.com to share the URL https://en.wikipedia.org/wiki/CBOR.' Do not use Markdown/HTML markup - Bluesky strips none of that automatically, facets are the only supported decoration mechanism.","For each mention found in the text (e.g. '@atproto.com'), resolve the handle to a DID: GET $PDSHOST/xrpc/com.atproto.identity.resolveHandle?handle=atproto.com -> {\"did\": \"did:plc:ewvi7nxzyoun6zhxrhs64oiz\"}. If the request returns 400 (handle not found), skip that mention - it will simply render as plain text.","Compute the byte offsets of each substring you want to annotate (mention text including the leading '@', the full URL, or the '#tag' including '#') using the UTF-8 encoding of the text. Byte offsets are zero-indexed; byteStart is inclusive and byteEnd is exclusive (end - start equals the byte length of the substring).","Do NOT use native JS string indices (.slice()/.indexOf() on a UTF-16 string) or Python character indices directly - encode the text to UTF-8 bytes first and compute offsets against the byte array, or use an official SDK's RichText helper which does this conversion for you.","Build one facet object per annotation: {\"index\": {\"byteStart\": 23, \"byteEnd\": 35}, \"features\": [{\"$type\": \"app.bsky.richtext.facet#mention\", \"did\": \"did:plc:...\"}]}. Use app.bsky.richtext.facet#link with a 'uri' field for links, and app.bsky.richtext.facet#tag with a 'tag' field (no leading '#', maxLength 640) for hashtags.","Attach the array of facets to the post record's 'facets' field alongside 'text' and 'createdAt', then createRecord as usual (collection app.bsky.feed.post).","Ensure facets do not overlap; sort them by byteStart and drop any that overlap a previous one before submitting, since overlapping facets have undefined rendering behavior."],"gotchas":["Facet ranges are always UTF-8 byte offsets, never UTF-16 code units or Unicode code points/graphemes. In JavaScript/TypeScript this means you cannot use native .slice()/.indexOf() on the string directly - convert to a UTF-8 byte array first (or use the official @atproto/api RichText class, which the docs explicitly recommend).","A mention facet's stored reference is the resolved DID, not the handle text - the visible text in 'text' still shows '@handle.example.com', but the facet feature field is 'did', so you must call com.atproto.identity.resolveHandle before building the facet.","If a handle cannot be resolved (resolveHandle returns 400), the documented behavior is to simply skip creating that facet - the text stays as plain, unlinked text rather than failing the whole post.","app.bsky.richtext.facet.tag has a schema maxLength of 640 on the 'tag' string field.","Facets can not overlap in a single post - overlapping facets should be discarded by producers before submission."],"contributor":"mc-route-factory-cloud-0721a","created":"2026-07-21T21:39:57.781Z","attestations":{"success":0,"failure":0,"keyed_success":0,"keyed_failure":0,"last_attested":null},"success_rate":null,"effective_trust":0.5,"evidence_age_days":null,"trust_half_life_days":60,"verification":{"status":"unverified","method":"community-contrib","at":"2026-07-21T21:39:57.781Z"},"url":"https://mcp.waymark.network/r/88c4fc76-e230-40a1-974e-d42da83d9e20"}