A community based topic aggregation platform built on atproto

feat: Reimplement richtext using Bluesky-inspired facet system

- Replace separate markup/mention/link lexicons with unified facet approach
- Use byte-indexed positioning (byteStart/byteEnd) for text annotations
- Support overlapping features: bold, italic, strikethrough, spoiler, mention, link
- Add native support for community mentions with \! prefix
- Update all schemas to use *Facets fields instead of *Markup
- Align with AT Protocol standards for better federation compatibility

BREAKING CHANGE: All richtext markup fields renamed to facets

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

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

+19
.claude/commands/create-pr.md
···
+
# Create Pull Request Command
+
+
Create a new branch, commit changes, and submit a pull request.
+
+
## Behavior
+
- Creates a new branch based on current changes
+
- Formats modified files using Biome
+
- Analyzes changes and automatically splits into logical commits when appropriate
+
- Each commit focuses on a single logical change or feature
+
- Creates descriptive commit messages for each logical unit
+
- Pushes branch to remote
+
- Creates pull request with proper summary and test plan
+
+
## Guidelines for Automatic Commit Splitting
+
- Split commits by feature, component, or concern
+
- Keep related file changes together in the same commit
+
- Separate refactoring from feature additions
+
- Ensure each commit can be understood independently
+
- Multiple unrelated changes should be split into separate commits
+9
.claude/commands/update-branch-name.md
···
+
# Update Branch Name
+
+
Follow these steps to update the current branch name:
+
+
1. Check differences between current branch and main branch HEAD using `git diff main...HEAD`
+
2. Analyze the changed files to understand what work is being done
+
3. Determine an appropriate descriptive branch name based on the changes
+
4. Update the current branch name using `git branch -m [new-branch-name]`
+
5. Verify the branch name was updated with `git branch`
+2 -2
cmd/validate-lexicon/main.go
···
"social.coves.actor.profile",
"social.coves.community.profile",
"social.coves.post.text",
-
"social.coves.richtext.markup",
+
"social.coves.richtext.facet",
}
if verbose {
···
func loadSchemasWithDebug(catalog *lexicon.BaseCatalog, schemaPath string, verbose bool) error {
var schemaFiles []string
-
// Collect all JSON schema files
+
// Collect all JSON schema filesred
err := filepath.Walk(schemaPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
+3 -7
internal/atproto/lexicon/social/coves/actor/profile.json
···
"maxLength": 2560,
"description": "User bio with rich text support"
},
-
"bioMarkup": {
+
"bioFacets": {
"type": "array",
"description": "Rich text annotations for bio",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"avatar": {
+3 -7
internal/atproto/lexicon/social/coves/actor/updateProfile.json
···
"maxLength": 2560,
"description": "User bio with rich text support"
},
-
"bioMarkup": {
+
"bioFacets": {
"type": "array",
"description": "Rich text annotations for bio",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"avatar": {
+3 -7
internal/atproto/lexicon/social/coves/community/profile.json
···
"maxLength": 10000,
"description": "Community description with rich text support"
},
-
"descriptionMarkup": {
+
"descriptionFacets": {
"type": "array",
"description": "Rich text annotations for description",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"avatar": {
+3 -7
internal/atproto/lexicon/social/coves/interaction/comment.json
···
"maxLength": 10000,
"description": "Comment text"
},
-
"markup": {
+
"facets": {
"type": "array",
"description": "Rich text annotations",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
}
}
+3 -7
internal/atproto/lexicon/social/coves/interaction/createComment.json
···
"maxLength": 30000,
"description": "Comment text"
},
-
"textMarkup": {
+
"textFacets": {
"type": "array",
"description": "Rich text annotations",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
}
}
+4 -8
internal/atproto/lexicon/social/coves/post/article.json
···
"maxLength": 50000,
"description": "Optional text content to accompany link"
},
-
"contentMarkup": {
+
"facets": {
"type": "array",
-
"description": "Rich text annotations for content",
+
"description": "Rich text annotations",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"tags": {
+3 -7
internal/atproto/lexicon/social/coves/post/create.json
···
"maxLength": 40000,
"description": "Post body text"
},
-
"textMarkup": {
+
"textFacets": {
"type": "array",
"description": "Rich text annotations for body",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"images": {
+3 -7
internal/atproto/lexicon/social/coves/post/get.json
···
"text": {
"type": "string"
},
-
"textMarkup": {
+
"textFacets": {
"type": "array",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"images": {
+3 -7
internal/atproto/lexicon/social/coves/post/image.json
···
"maxLength": 10000,
"description": "Optional caption or description"
},
-
"captionMarkup": {
+
"captionFacets": {
"type": "array",
"description": "Rich text annotations for caption",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"tags": {
+17 -10
internal/atproto/lexicon/social/coves/post/microblog.json
···
"maxLength": 5000,
"description": "Microblog post content (plain text or markdown)"
},
-
"contentMarkup": {
+
"facets": {
"type": "array",
-
"description": "Rich text annotations for content",
+
"description": "Rich text annotations",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"title": {
···
"type": "array",
"description": "User mentions in the post",
"items": {
-
"type": "ref",
-
"ref": "social.coves.richtext.mention"
+
"type": "object",
+
"required": ["did"],
+
"properties": {
+
"did": {
+
"type": "string",
+
"format": "did",
+
"description": "DID of the mentioned user"
+
},
+
"handle": {
+
"type": "string",
+
"description": "Handle at time of mention"
+
}
+
}
}
},
"embed": {
+4 -8
internal/atproto/lexicon/social/coves/post/text.json
···
"maxLength": 50000,
"description": "Markdown-formatted post content"
},
-
"contentMarkup": {
+
"facets": {
"type": "array",
-
"description": "Rich text annotations for content",
+
"description": "Rich text annotations",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"tags": {
+3 -7
internal/atproto/lexicon/social/coves/post/update.json
···
"maxLength": 100000,
"description": "Updated body text"
},
-
"textMarkup": {
+
"textFacets": {
"type": "array",
"description": "Updated rich text annotations",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"tags": {
+3 -7
internal/atproto/lexicon/social/coves/post/video.json
···
"maxLength": 10000,
"description": "Optional caption or description"
},
-
"captionMarkup": {
+
"captionFacets": {
"type": "array",
"description": "Rich text annotations for caption",
"items": {
-
"type": "union",
-
"refs": [
-
"social.coves.richtext.markup",
-
"social.coves.richtext.mention",
-
"social.coves.richtext.link"
-
]
+
"type": "ref",
+
"ref": "social.coves.richtext.facet"
}
},
"tags": {
+103
internal/atproto/lexicon/social/coves/richtext/facet.json
···
+
{
+
"lexicon": 1,
+
"id": "social.coves.richtext.facet",
+
"defs": {
+
"main": {
+
"type": "object",
+
"description": "Annotation of a sub-string within rich text",
+
"required": ["index", "features"],
+
"properties": {
+
"index": {
+
"type": "ref",
+
"ref": "#byteSlice"
+
},
+
"features": {
+
"type": "array",
+
"description": "Features applied to this text range",
+
"items": {
+
"type": "union",
+
"refs": [
+
"#mention",
+
"#link",
+
"#bold",
+
"#italic",
+
"#strikethrough",
+
"#spoiler"
+
]
+
}
+
}
+
}
+
},
+
"byteSlice": {
+
"type": "object",
+
"description": "Specifies the sub-string range via byte indices",
+
"required": ["byteStart", "byteEnd"],
+
"properties": {
+
"byteStart": {
+
"type": "integer",
+
"minimum": 0,
+
"description": "Inclusive start position in UTF-8 bytes"
+
},
+
"byteEnd": {
+
"type": "integer",
+
"minimum": 0,
+
"description": "Exclusive end position in UTF-8 bytes"
+
}
+
}
+
},
+
"mention": {
+
"type": "object",
+
"description": "Facet feature for user or community mentions",
+
"required": ["did"],
+
"properties": {
+
"did": {
+
"type": "string",
+
"format": "did",
+
"description": "DID of the mentioned user (@) or community (!)"
+
},
+
"handle": {
+
"type": "string",
+
"description": "Handle at time of mention (may change)"
+
}
+
}
+
},
+
"link": {
+
"type": "object",
+
"description": "Facet feature for hyperlinks",
+
"required": ["uri"],
+
"properties": {
+
"uri": {
+
"type": "string",
+
"format": "uri",
+
"description": "Target URI of the link"
+
}
+
}
+
},
+
"bold": {
+
"type": "object",
+
"description": "Bold text formatting",
+
"properties": {}
+
},
+
"italic": {
+
"type": "object",
+
"description": "Italic text formatting",
+
"properties": {}
+
},
+
"strikethrough": {
+
"type": "object",
+
"description": "Strikethrough text formatting",
+
"properties": {}
+
},
+
"spoiler": {
+
"type": "object",
+
"description": "Hidden/spoiler text that requires user interaction to reveal",
+
"properties": {
+
"reason": {
+
"type": "string",
+
"maxLength": 128,
+
"description": "Optional explanation of what's hidden"
+
}
+
}
+
}
+
}
+
}
-22
internal/atproto/lexicon/social/coves/richtext/link.json
···
-
{
-
"lexicon": 1,
-
"id": "social.coves.richtext.link",
-
"defs": {
-
"main": {
-
"type": "object",
-
"description": "Link within text content",
-
"required": ["index", "uri"],
-
"properties": {
-
"index": {
-
"type": "ref",
-
"ref": "social.coves.richtext.markup#byteSlice"
-
},
-
"uri": {
-
"type": "string",
-
"format": "uri",
-
"description": "Target URI of the link"
-
}
-
}
-
}
-
}
-
}
-71
internal/atproto/lexicon/social/coves/richtext/markup.json
···
-
{
-
"lexicon": 1,
-
"id": "social.coves.richtext.markup",
-
"defs": {
-
"main": {
-
"type": "object",
-
"description": "Text markup and formatting annotations",
-
"required": ["index", "features"],
-
"properties": {
-
"index": {
-
"type": "ref",
-
"ref": "#byteSlice"
-
},
-
"features": {
-
"type": "array",
-
"description": "Formatting features applied to this text range",
-
"items": {
-
"type": "union",
-
"refs": ["#bold", "#italic", "#code", "#strikethrough", "#spoiler"]
-
}
-
}
-
}
-
},
-
"byteSlice": {
-
"type": "object",
-
"description": "Byte position and length of text slice",
-
"required": ["byteStart", "byteLength"],
-
"properties": {
-
"byteStart": {
-
"type": "integer",
-
"minimum": 0
-
},
-
"byteLength": {
-
"type": "integer",
-
"minimum": 0
-
}
-
}
-
},
-
"bold": {
-
"type": "object",
-
"description": "Bold text formatting",
-
"properties": {}
-
},
-
"italic": {
-
"type": "object",
-
"description": "Italic text formatting",
-
"properties": {}
-
},
-
"code": {
-
"type": "object",
-
"description": "Code/monospace text formatting",
-
"properties": {}
-
},
-
"strikethrough": {
-
"type": "object",
-
"description": "Strikethrough text formatting",
-
"properties": {}
-
},
-
"spoiler": {
-
"type": "object",
-
"description": "Spoiler text that is hidden until clicked",
-
"properties": {
-
"reason": {
-
"type": "string",
-
"maxLength": 128,
-
"description": "Optional explanation of what's hidden"
-
}
-
}
-
}
-
}
-
}
-30
internal/atproto/lexicon/social/coves/richtext/mention.json
···
-
{
-
"lexicon": 1,
-
"id": "social.coves.richtext.mention",
-
"defs": {
-
"main": {
-
"type": "object",
-
"description": "Mention of a user or community in text",
-
"required": ["index", "did"],
-
"properties": {
-
"index": {
-
"type": "ref",
-
"ref": "social.coves.richtext.markup#byteSlice"
-
},
-
"did": {
-
"type": "string",
-
"format": "did",
-
"description": "DID of the mentioned user or community"
-
},
-
"handle": {
-
"type": "string",
-
"description": "Handle at time of mention (may change)"
-
},
-
"federatedFrom": {
-
"type": "string",
-
"description": "Platform the mentioned entity is from (bluesky, lemmy, coves)"
-
}
-
}
-
}
-
}
-
}
validate-lexicon

This is a binary file and will not be displayed.