In atproto, users can hold data of various record types, which can be referenced in a unified way.
However, handling unknown record types remains difficult.
Most Bluesky clients ignore embedded records except for a few specific collections.
WhiteWind displays any records as reactions, but it can only extract comment text from a limited set of collections.
Bluesky has hinted at a possible solution, but it’s unlikely to be implemented anytime soon:
https://bsky.app/profile/pfrazee.com/post/3ky4vlbay742m
As a possible solution, I propose introducing a lexicon to declare views for records.
This concept is inspired by Hopper, but using atproto records provides several advantages:
-
Aggregation through atproto infrastructure allows view definitions from third parties or other users to be included as candidates.
- Even without aggregation, definitions by the lexicon owner can be obtained through lexicon resolution, and users can also fetch their own definitions from their repositories.
-
Third parties adding new view formats can define their own schemas.
-
It can be used without hosting a server under the domain of the lexicon’s NSID.
As an extended use case, this could be linked from profiles or content records to indicate the recommended viewing environment or the viewer the user prefers.
Lexicon
Below is a prototype to illustrate the concept:
{
"lexicon": 1,
"id": "example.lexicon.view.record",
"defs": {
"main": {
"type": "record",
"key": "nsid",
"record": {
"type": "object",
"required": ["views"],
"description": "record view definition",
"properties": {
"views": {
"type": "array",
"items": {
"type": "union",
"refs": [ "#template", "#field" ]
}
}
}
}
},
"text":{
"type": "object",
"required": ["text"],
"properties": {
"text": { "type": "string" },
"url": { "type": "string" }
}
},
"link":{
"type": "object",
"required": ["url"],
"properties": {
"text": { "type": "string" },
"url": { "type": "string" }
}
}
}
}
// at://did:example:xxx/example.lexicon.view.record/app.bsky.feed.post
{
"$type": "example.lexicon.view.record",
"views": [
{
"$type": "example.lexicon.view.record#text",
"text": "${handle}: ${body.record.text}"
},
{
"$type": "example.lexicon.view.record#text",
"text": "view this post on bsky.app",
"url": "https://bsky.app/profile/${did}/post/${rkey}"
}
]
}
Open Questions
-
Is it appropriate to use the rkey as the NSID?
-
Following the bsky style (separate records per item) allows reuse of others’ definitions but increases resolution complexity.
-
Allowing multiple view-definition records for the same collection may improve flexibility for future extensions.
-
-
Would defining views for non-record entities be useful?
-
Depending on the case, we might want to handle related types or allow linking between different object types.
-
For DIDs, the current views in PDSls or bsky.app might be sufficient, but selecting contexts like Tangled could be helpful.
-
In addition to per-record views, defining collection-level views might also make sense.
-
Defining views for arbitrary objects could be useful for client-specific fields, though it would require a merging mechanism.
-
-
Is the current definition format sufficient?
-
Since it uses open unions and can be updated later, this point is not high priority.
-
Rich views (like oEmbed-style content) may be in demand, but I haven’t found an appropriate record representation yet.
-
Pointing to endpoints such as
public.api.bsky.app/xrpc/app.bsky.actor.getProfilemight be useful, but should this be separated from human-facing views? -
You may be able to create html views that render rich text. It should be distinguished from plain text representation.
-
-
How expressive should template strings be?
-
Dereferencing at-uris could help with records that have no views of their own (like
likerecords), but is that really necessary? -
It’s probably good to support optional fields and lists, but adding full conditionals or loops (map) might be excessive.
-
If we go further, we might end up defining full components like AT Profile, which should probably be treated as another type.
-
-
Should templates be structured rather than stored in a single string field?
-
How can we respect access restrictions such as
!no-unauthenticated?
Feedback and alternative approaches are welcome — including entirely different ideas.