Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions community/lexicon/app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# community.lexicon.app

This set of Lexicon schemas describes apps built on or for the AT Protocol.

## Records

* `profile`: A canonical app profile published by the app's DID at rkey
`self`.

* `profileLocalization`: Locale-specific metadata for a canonical app profile.
These records should be published by the same DID as the profile. Publishers
should use normalized BCP 47 language tags as rkeys when possible, such as
`fr` or `pt-BR`; consumers should trust the record's `locale` field over the
rkey.

* `entry`: A community or third-party app listing. Any account can publish an
entry, including entries for apps they do not own.

## Canonical profiles

Entries can name a `profileDid`. Consumers can derive the canonical profile URI
from that DID:

```text
at://<profileDid>/community.lexicon.app.profile/self
```

An entry without `profileDid` should be treated as a standalone third-party
listing. A `profileDid` is a pointer to where a canonical profile may exist, not
proof of ownership or verification by itself.

## Internationalization

Entries can include a `locale` field when the listing is intended for a
particular language or regional audience. App publishers can add
`profileLocalization` records for authoritative localized names, descriptions,
links, images, and Web App Manifest URIs.

Publishers SHOULD use a lowercase BCP 47 language tag as the record rkey (e.g.
`fr`, `pt-br`, `zh-hant`) so the rkey acts as a natural uniqueness key per
locale. Publishers SHOULD publish at most one `profileLocalization` record per
locale. If duplicate records exist for the same locale, consumers SHOULD prefer
the one with the most recent `updatedAt` or `createdAt` timestamp.

## Rich app metadata

Records can include an `images` array (up to 24 items) for icons, logos, hero
images, screenshots, banners, social cards, app store visuals, ads, or other
directory media. Each item MUST include `alt` text and exactly one of `image`
(an ATProto blob) or `uri` (a remote URL). Consumers MUST ignore items that
carry neither or both. `purpose` and `aspectRatio` are optional per item;
multiple items may share the same `purpose` (e.g. several screenshots or hero
variants are all valid).

Records may also include `webManifestUri` to point at an HTTPS Web App Manifest.
Manifests can prefill or augment install behavior, icons, screenshots,
language, and platform or form-factor metadata without making manifests the only
way to describe visual assets.

## Trust and verification

`profileDid` in an `entry` record is an unverified claim made by whoever
published the entry — it is not proof that the named DID endorses the listing.
Directories SHOULD treat it purely as a hint. Before labeling an entry
"official" or "verified", directories SHOULD:

1. Fetch the record at `at://<profileDid>/community.lexicon.app.profile/self`
and prefer its fields over any data copied into the entry.
2. Verify ownership out of band using methods such as `rel=me`, `.well-known`
resources, or other trust policies.

Verification is otherwise intentionally out of scope for these records.
149 changes: 149 additions & 0 deletions community/lexicon/app/defs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
{
"lexicon": 1,
"id": "community.lexicon.app.defs",
"defs": {
"link": {
"type": "object",
"description": "A labeled URI associated with an app.",
"required": ["uri"],
"properties": {
"uri": {
"type": "string",
"format": "uri",
"description": "Destination URI."
},
"label": {
"type": "string",
"maxLength": 100,
"maxGraphemes": 50,
"description": "Human-readable label for the URI."
}
}
},
"image": {
"type": "object",
"description": "An image associated with an app, including accessibility and display metadata. Exactly one of `image` (ATProto blob) or `uri` (remote URL) MUST be present. Consumers SHOULD ignore image items that have neither or both.",
"required": ["alt"],
"properties": {
"purpose": {
"type": "string",
"knownValues": [
"community.lexicon.app.defs#purposeIcon",
"community.lexicon.app.defs#purposeLogo",
"community.lexicon.app.defs#purposeHero",
"community.lexicon.app.defs#purposeScreenshot",
"community.lexicon.app.defs#purposeBanner",
"community.lexicon.app.defs#purposeSocialCard",
"community.lexicon.app.defs#purposeAppStore",
"community.lexicon.app.defs#purposeAd",
"community.lexicon.app.defs#purposeOther"
Comment thread
pixeline marked this conversation as resolved.
Outdated
],
"description": "How directories and stores should use the image."
},
"image": {
"type": "blob",
"description": "The raw image file.",
"accept": ["image/*"],
"maxSize": 2000000
},
"uri": {
"type": "string",
"format": "uri",
"description": "Remote image URI."
},
"alt": {
"type": "string",
"maxLength": 1000,
"maxGraphemes": 300,
"description": "Alt text description of the image, for accessibility."
},
"aspectRatio": {
"type": "ref",
"ref": "#aspectRatio"
}
}
},
"aspectRatio": {
"type": "object",
"description": "width:height represents an aspect ratio. It may be approximate.",
"required": ["width", "height"],
"properties": {
"width": {
"type": "integer",
"minimum": 1
},
"height": {
"type": "integer",
"minimum": 1
}
}
},
"purposeIcon": {
"type": "token",
"description": "A small square icon representing the app, typically used in launchers, lists, and tab bars."
},
"purposeLogo": {
"type": "token",
"description": "A logotype or wordmark for the app."
},
"purposeHero": {
"type": "token",
"description": "A large promotional or feature image, typically used at the top of a directory listing."
},
"purposeScreenshot": {
"type": "token",
"description": "A screenshot of the app's UI, used in directory and store listings."
},
"purposeBanner": {
"type": "token",
"description": "A wide banner image, such as a header image for a profile or listing page."
},
"purposeSocialCard": {
"type": "token",
"description": "An image sized and formatted for social media sharing previews (Open Graph, Twitter Card, etc.)."
},
"purposeAppStore": {
"type": "token",
"description": "A promotional image formatted for a native app store listing."
},
"purposeAd": {
"type": "token",
"description": "A promotional or advertising image."
},
"purposeOther": {
"type": "token",
"description": "An image whose purpose does not fit any other known value."
},
"status": {
"type": "string",
"description": "Current release or maintenance status of an app.",
"knownValues": [
"community.lexicon.app.defs#unreleased",
"community.lexicon.app.defs#preview",
"community.lexicon.app.defs#released",
"community.lexicon.app.defs#unmaintained",
"community.lexicon.app.defs#discontinued"
]
},
"unreleased": {
"type": "token",
"description": "The app has been announced but is not yet available."
},
"preview": {
"type": "token",
"description": "The app is available as an alpha, beta, early access, or preview release."
},
"released": {
"type": "token",
"description": "The app is generally available."
},
"unmaintained": {
"type": "token",
"description": "The app may still be available, but is no longer actively maintained."
},
"discontinued": {
"type": "token",
"description": "The app is no longer available or supported."
}
}
}
88 changes: 88 additions & 0 deletions community/lexicon/app/entry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"lexicon": 1,
"id": "community.lexicon.app.entry",
"defs": {
"main": {
"type": "record",
"description": "A third-party or community listing for an app built on or for the AT Protocol.",
"key": "tid",
"record": {
"type": "object",
"required": ["name", "links", "createdAt"],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"maxGraphemes": 100,
"description": "The display name of the app."
},
"description": {
"type": "string",
"maxLength": 3000,
"maxGraphemes": 300,
"description": "A short description of what the app does."
},
"images": {
"type": "array",
"maxLength": 24,
"description": "Visual assets for directories and stores, such as icons, hero images, screenshots, banners, and social cards.",
"items": {
"type": "ref",
"ref": "community.lexicon.app.defs#image"
}
},
"tags": {
"type": "array",
"maxLength": 10,
Comment thread
pixeline marked this conversation as resolved.
"items": {
"type": "string",
"maxLength": 64,
"maxGraphemes": 32
},
"description": "Open discovery tags for filtering and search, preferably lowercase."
},
"status": {
"type": "ref",
"ref": "community.lexicon.app.defs#status",
"description": "Current release or maintenance status of the app."
},
"profileDid": {
"type": "string",
"format": "did",
"description": "DID expected to publish the canonical app profile at community.lexicon.app.profile/self."
},
"locale": {
"type": "string",
"format": "language",
"description": "BCP 47 language tag indicating the language or regional audience this entry is intended for."
},
"links": {
"type": "array",
"minLength": 1,
"maxLength": 12,
"description": "Relevant destinations for the app. The first link should be the primary destination.",
"items": {
"type": "ref",
"ref": "community.lexicon.app.defs#link"
}
},
"webManifestUri": {
"type": "string",
"format": "uri",
"description": "HTTPS URI of a Web App Manifest for richer install and directory metadata, such as icons and screenshots."
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "Client-declared timestamp when this entry was created."
},
"updatedAt": {
"type": "string",
"format": "datetime",
"description": "Client-declared timestamp when this entry was last updated."
}
}
}
}
}
}
78 changes: 78 additions & 0 deletions community/lexicon/app/profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"lexicon": 1,
"id": "community.lexicon.app.profile",
"defs": {
"main": {
"type": "record",
"description": "The canonical self-published profile for an app built on or for the AT Protocol.",
"key": "literal:self",
"record": {
"type": "object",
"required": ["name", "links", "createdAt"],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"maxGraphemes": 100,
"description": "The display name of the app."
},
"description": {
"type": "string",
"maxLength": 3000,
"maxGraphemes": 300,
"description": "A short description of what the app does."
},
"images": {
"type": "array",
"maxLength": 24,
"description": "Visual assets for directories and stores, such as icons, hero images, screenshots, banners, and social cards.",
"items": {
"type": "ref",
"ref": "community.lexicon.app.defs#image"
}
},
"tags": {
"type": "array",
"maxLength": 10,
"items": {
"type": "string",
"maxLength": 64,
"maxGraphemes": 32
},
"description": "Open discovery tags for filtering and search, preferably lowercase."
},
"status": {
"type": "ref",
"ref": "community.lexicon.app.defs#status",
"description": "Current release or maintenance status of the app."
},
"links": {
"type": "array",
"minLength": 1,
"maxLength": 12,
"description": "Relevant destinations for the app. The first link should be the primary destination.",
"items": {
"type": "ref",
"ref": "community.lexicon.app.defs#link"
}
},
Comment thread
pixeline marked this conversation as resolved.
"webManifestUri": {
"type": "string",
"format": "uri",
"description": "HTTPS URI of a Web App Manifest for richer install and directory metadata, such as icons and screenshots."
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "Client-declared timestamp when this profile was created."
},
"updatedAt": {
"type": "string",
"format": "datetime",
"description": "Client-declared timestamp when this profile was last updated."
}
}
}
}
}
}
Loading