appendBlueskyText

fun AnnotatedString.Builder.appendBlueskyText(text: String, facets: List<Facet>?, onFacet: AnnotatedString.Builder.(feature: FacetFeaturesUnion, startChar: Int, endChar: Int, slice: String) -> Unit)

Append Bluesky text to this AnnotatedString.Builder and invoke onFacet for every valid facet feature, sorted by byteStart ascending.

This is the lowest-level primitive in the Compose helper. The full text is appended to the builder once, up front. Callers handle styling, inline content, link/string annotations themselves inside the onFacet lambda — this primitive's job is only to translate UTF-8 byte offsets (the facet contract) into UTF-16 char offsets (the AnnotatedString contract) correctly.

The byte-to-char mapping is computed once via a one-pass walker (see Utf8CharBoundaryTable) and shared across all facets, so this function is O(text.length + facets.size · log codepoints) and allocates no transient Strings for boundary lookups.

Silent skip contract. Malformed facets are dropped without throwing:

  • byteStart < 0, byteEnd <= byteStart, or byteEnd > utf8 length

  • byte offsets that fall inside a codepoint's UTF-8 byte sequence

  • facets with an empty features list

Unknown feature variants (FacetFeaturesUnion.Unknown) are not dropped here — they are passed to onFacet so callers can decide whether to render them or skip them. The higher-level buildBlueskyAnnotatedString skips unknowns silently.

Sort order guarantee. onFacet is invoked in byteStart ascending order. Ties between facets at the same byteStart preserve their original order in the input list (stable sort). Ties between features inside a single facet preserve their features array order. This lets callers stream output (e.g. appendInlineContent) without sorting themselves.

The slice parameter on onFacet is text.substring(startChar, endChar), precomputed so callers building inline content (icon next to a @handle, ellipsized URLs) don't need to re-substring with their own bounds.

Parameters

text

the post's plain text. Appended to the builder up front.

facets

facet annotations from the post record (may be null).

onFacet

invoked for each facet feature, with the typed sealed-parent FacetFeaturesUnion, the translated UTF-16 char range [startChar, endChar), and the matched substring slice. Runs with the builder as receiver — call addStyle, addLink, addStringAnnotation, etc. to attach behavior to the range.