A community based topic aggregation platform built on atproto

refactor(lexicon): align richtext facets with atProto best practices

Align richtext facet lexicon with atProto Lexinomicon style guide:

- Remove $type from required fields (automatically added by SDK for union discrimination)
- Remove handle field from mention type (use persistent DIDs only per best practices)
- Add maxGraphemes constraint to spoiler reason field for proper internationalization
- Update descriptions to match Bluesky documentation patterns
- Update tests to remove handle field references

References: https://github.com/bluesky-social/atproto/discussions/4245

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+12 -51
internal
atproto
lexicon
social
coves
+9 -46
internal/atproto/lexicon/social/coves/richtext/facet.json
···
},
"mention": {
"type": "object",
-
"description": "Facet feature for user or community mentions",
-
"required": ["$type", "did"],
+
"description": "Facet feature for mention of a user or community. The text is usually a handle with '@' (user) or '!' (community) prefix, but the facet reference is a DID.",
+
"required": ["did"],
"properties": {
-
"$type": {
-
"type": "string",
-
"const": "social.coves.richtext.facet#mention"
-
},
"did": {
"type": "string",
"format": "did",
-
"description": "DID of the mentioned user (@) or community (!)"
-
},
-
"handle": {
-
"type": "string",
-
"description": "Handle at time of mention (may change)"
+
"description": "DID of the mentioned user or community"
}
}
},
"link": {
"type": "object",
-
"description": "Facet feature for hyperlinks",
-
"required": ["$type", "uri"],
+
"description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.",
+
"required": ["uri"],
"properties": {
-
"$type": {
-
"type": "string",
-
"const": "social.coves.richtext.facet#link"
-
},
"uri": {
"type": "string",
"format": "uri",
···
},
"bold": {
"type": "object",
-
"description": "Bold text formatting",
-
"required": ["$type"],
-
"properties": {
-
"$type": {
-
"type": "string",
-
"const": "social.coves.richtext.facet#bold"
-
}
-
}
+
"description": "Bold text formatting"
},
"italic": {
"type": "object",
-
"description": "Italic text formatting",
-
"required": ["$type"],
-
"properties": {
-
"$type": {
-
"type": "string",
-
"const": "social.coves.richtext.facet#italic"
-
}
-
}
+
"description": "Italic text formatting"
},
"strikethrough": {
"type": "object",
-
"description": "Strikethrough text formatting",
-
"required": ["$type"],
-
"properties": {
-
"$type": {
-
"type": "string",
-
"const": "social.coves.richtext.facet#strikethrough"
-
}
-
}
+
"description": "Strikethrough text formatting"
},
"spoiler": {
"type": "object",
"description": "Hidden/spoiler text that requires user interaction to reveal",
-
"required": ["$type"],
"properties": {
-
"$type": {
-
"type": "string",
-
"const": "social.coves.richtext.facet#spoiler"
-
},
"reason": {
"type": "string",
"maxLength": 128,
+
"maxGraphemes": 32,
"description": "Optional explanation of what's hidden"
}
}
+3 -5
internal/atproto/lexicon/social/coves/richtext/facet_test.go
···
},
"features": [{
"$type": "social.coves.richtext.facet#mention",
-
"did": "did:plc:example123",
-
"handle": "alice.bsky.social"
+
"did": "did:plc:example123"
}]
}`,
wantErr: false,
···
name: "mention",
typeName: "social.coves.richtext.facet#mention",
feature: map[string]interface{}{
-
"$type": "social.coves.richtext.facet#mention",
-
"did": "did:plc:example123",
-
"handle": "alice.bsky.social",
+
"$type": "social.coves.richtext.facet#mention",
+
"did": "did:plc:example123",
},
},
{