Standard.site landing page built in Next.js
0

Configure Feed

Select the types of activity you want to include in your feed.

docs: add documentation for new features

+422 -80
+9 -4
app/components/docs/ClickableHeading.tsx
··· 6 6 interface ClickableHeadingProps { 7 7 level: 1 | 2 | 3 | 4 8 8 children: React.ReactNode 9 + text?: string 9 10 } 10 11 11 - export function ClickableHeading({ level, children }: ClickableHeadingProps) { 12 + export function ClickableHeading({ level, children, text }: ClickableHeadingProps) { 12 13 const [copied, setCopied] = useState(false) 13 14 14 - const text = typeof children === 'string' ? children : extractText(children) 15 - const id = generateSlug(text) 15 + const slugText = text ?? (typeof children === 'string' ? children : extractText(children)) 16 + const id = generateSlug(slugText) 16 17 17 18 const handleClick = async () => { 18 19 const url = `${window.location.origin}${window.location.pathname}#${id}` ··· 64 65 if (typeof node === 'number') return String(node) 65 66 if (Array.isArray(node)) return node.map(extractText).join('') 66 67 if (node && typeof node === 'object' && 'props' in node) { 67 - return extractText((node as { props: { children: React.ReactNode } }).props.children) 68 + const el = node as { type?: unknown; props: { children: React.ReactNode } } 69 + if (el.type && typeof el.type === 'function' && (el.type as { anchorSkip?: boolean }).anchorSkip) { 70 + return '' 71 + } 72 + return extractText(el.props.children) 68 73 } 69 74 return '' 70 75 }
+9
app/components/docs/Updated.tsx
··· 1 + export function Updated({ label = 'Updated' }: { label?: string }) { 2 + return ( 3 + <span className="inline-flex shrink-0 items-center rounded-sm bg-yellow-200 px-2 pt-[0.4rem] pb-[0.3rem] text-xs font-medium italic uppercase tracking-wider leading-none text-yellow-800 transition-opacity group-hover:opacity-50"> 4 + {label} 5 + </span> 6 + ) 7 + } 8 + 9 + Updated.anchorSkip = true
+1
app/components/docs/index.ts
··· 5 5 export { StandardSite } from './StandardSite' 6 6 export { ClickableHeading } from './ClickableHeading' 7 7 export { DocsPageMenu } from './DocsPageMenu' 8 + export { Updated } from './Updated'
+3 -3
app/components/sections/DefinitionsSection.tsx
··· 38 38 </h2> 39 39 40 40 <p className="text-base sm:text-xl leading-snug tracking-tight text-muted"> 41 - We currently define three main lexicons that cover the core building 42 - blocks of long-form platforms: where content lives, what it 43 - contains, and how users connect with publications. 41 + We currently define a small set of lexicons that cover the core building 42 + blocks of long-form platforms: where content lives, what it contains, and how 43 + users connect with publications and documents they care about. 44 44 </p> 45 45 46 46 <TabbedLexiconViewer tabs={ tabs } allSchemas={ allSchemas } />
+9 -1
app/components/sections/HeroSection.tsx
··· 34 34 To start, view the definitions <ArrowRightIcon className="transform transition-all md:group-hover:translate-x-2 size-4 sm:size-6 inline"/> 35 35 </a> 36 36 </AnimateIn> 37 + <AnimateIn delay={ 0.25 }> 38 + <a 39 + href="/docs" 40 + className="group font-medium text-base sm:text-lg leading-snug tracking-tight text-muted hover:text-base-content hover:underline" 41 + > 42 + Skip to docs <ArrowRightIcon className="transform transition-all md:group-hover:translate-x-2 size-3.5 sm:size-4 inline"/> 43 + </a> 44 + </AnimateIn> 37 45 </div> 38 46 39 47 <div className="flex flex-col gap-4"> 40 48 <AnimateIn as="p" delay={ 0.3 } className="text-sm sm:text-base leading-snug tracking-tight text-muted-content"> 41 - Being implemented by multiple platforms: 49 + Already implemented by multiple platforms: 42 50 </AnimateIn> 43 51 44 52 <AnimateIn delay={ 0.4 } className="@lg/hero:grid-cols-3 grid grid-cols-2 gap-1 rounded-2xl border border-border bg-base-200 p-1">
+1
app/data/content.ts
··· 77 77 'site.standard.publication', 78 78 'site.standard.document', 79 79 'site.standard.graph.subscription', 80 + 'site.standard.graph.recommend', 80 81 ]
+1
app/data/docs-nav.ts
··· 24 24 { label: 'Publication', href: '/docs/lexicons/publication' }, 25 25 { label: 'Document', href: '/docs/lexicons/document' }, 26 26 { label: 'Subscription', href: '/docs/lexicons/subscription' }, 27 + { label: 'Recommend', href: '/docs/lexicons/recommend' }, 27 28 { label: 'Theme', href: '/docs/lexicons/theme' }, 28 29 ], 29 30 },
+34
app/data/lexicon-overrides.ts
··· 8 8 name: 'Name of the publication.', 9 9 description: 'Brief description of the publication.', 10 10 basicTheme: 'Simplified publication theme for tools and apps to utilize when displaying content.', 11 + labels: 'Self-label values for this publication. Effectively content warnings.', 11 12 preferences: 'Object containing platform specific preferences (with a few shared properties).', 12 13 }, 13 14 'site.standard.theme.basic': { ··· 29 30 textContent: 'Plaintext representation of the documents contents. Should not contain markdown or other formatting.', 30 31 bskyPostRef: 'Strong reference to a Bluesky post. Useful to keep track of comments off-platform.', 31 32 tags: 'Array of strings used to tag or categorize the document. Avoid prepending tags with hashtags.', 33 + links: 'Open union describing relationships between this document and external resources.', 34 + labels: 'Self-label values for this document. Effectively content warnings.', 35 + contributors: 'List of additional contributors to this document.', 32 36 publishedAt: 'Timestamp of the documents publish time.', 33 37 updatedAt: 'Timestamp of the documents last edit.', 38 + }, 39 + 'site.standard.document#contributor': { 40 + did: 'DID of the contributor.', 41 + role: 'Role of the contributor (ex: `editor`, `translator`).', 42 + displayName: 'Optional display name override for the contributor.', 43 + }, 44 + 'site.standard.graph.subscription': { 45 + publication: 'AT-URI reference to the publication record being subscribed to.', 46 + createdAt: 'Timestamp marking when the subscription was created.', 47 + }, 48 + 'site.standard.graph.recommend': { 49 + document: 'AT-URI reference to the document record being recommended.', 50 + createdAt: 'Timestamp marking when the recommendation was created.', 34 51 }, 35 52 } 36 53 ··· 55 72 'name', 56 73 'description', 57 74 'basicTheme', 75 + 'labels', 58 76 'preferences', 59 77 ], 60 78 'site.standard.document': [ ··· 67 85 'textContent', 68 86 'bskyPostRef', 69 87 'tags', 88 + 'links', 89 + 'labels', 90 + 'contributors', 70 91 'publishedAt', 71 92 'updatedAt', 93 + ], 94 + 'site.standard.document#contributor': [ 95 + 'did', 96 + 'role', 97 + 'displayName', 98 + ], 99 + 'site.standard.graph.subscription': [ 100 + 'publication', 101 + 'createdAt', 102 + ], 103 + 'site.standard.graph.recommend': [ 104 + 'document', 105 + 'createdAt', 72 106 ], 73 107 'site.standard.theme.basic': [ 74 108 'background',
+24
app/data/lexicons/authFull.json
··· 1 + { 2 + "id": "site.standard.authFull", 3 + "defs": { 4 + "main": { 5 + "type": "permission-set", 6 + "title": "Standard.site", 7 + "detail": "Manage your publications, documents, subscriptions, and recommends.", 8 + "permissions": [ 9 + { 10 + "type": "permission", 11 + "resource": "repo", 12 + "collection": [ 13 + "site.standard.publication", 14 + "site.standard.document", 15 + "site.standard.graph.subscription", 16 + "site.standard.graph.recommend" 17 + ] 18 + } 19 + ] 20 + } 21 + }, 22 + "$type": "com.atproto.lexicon.schema", 23 + "lexicon": 1 24 + }
+22
app/data/lexicons/authSocial.json
··· 1 + { 2 + "id": "site.standard.authSocial", 3 + "defs": { 4 + "main": { 5 + "type": "permission-set", 6 + "title": "Standard.site", 7 + "detail": "Manage your publication subscriptions and document recommendations.", 8 + "permissions": [ 9 + { 10 + "type": "permission", 11 + "resource": "repo", 12 + "collection": [ 13 + "site.standard.graph.subscription", 14 + "site.standard.graph.recommend" 15 + ] 16 + } 17 + ] 18 + } 19 + }, 20 + "$type": "com.atproto.lexicon.schema", 21 + "lexicon": 1 22 + }
+48 -7
app/data/lexicons/document.json
··· 14 14 "properties": { 15 15 "path": { 16 16 "type": "string", 17 - "description": "Combine with `site` or publication url to construct a canonical `URL` to the document. A slash should be included at the beginning of this value." 17 + "description": "Combine with site or publication url to construct a canonical URL to the document. Prepend with a leading slash." 18 18 }, 19 19 "site": { 20 20 "type": "string", 21 21 "format": "uri", 22 - "description": "Points to a publication record `at://` or a publication url `https://` for loose documents. Avoid trailing slashes." 22 + "description": "Points to a publication record (at://) or a publication url (https://) for loose documents. Avoid trailing slashes." 23 23 }, 24 24 "tags": { 25 25 "type": "array", 26 26 "items": { 27 - "type": "string" 27 + "type": "string", 28 + "maxLength": 1280, 29 + "maxGraphemes": 128 28 30 }, 29 - "maxLength": 1280, 30 - "description": "Array of strings used to tag or categorize the document. Avoid prepending tags with hashtags.", 31 - "maxGraphemes": 128 31 + "description": "Array of strings used to tag or categorize the document. Avoid prepending tags with hashtags." 32 + }, 33 + "links": { 34 + "refs": [], 35 + "type": "union", 36 + "description": "Array of values describing relationships between this document and external resources" 32 37 }, 33 38 "title": { 34 39 "type": "string", ··· 36 41 "description": "Title of the document.", 37 42 "maxGraphemes": 500 38 43 }, 44 + "labels": { 45 + "refs": [ 46 + "com.atproto.label.defs#selfLabels" 47 + ], 48 + "type": "union", 49 + "description": "Self-label values for this post. Effectively content warnings." 50 + }, 39 51 "content": { 40 52 "refs": [], 41 53 "type": "union", 42 54 "closed": false, 43 - "description": "Open union used to define the record's content. Each entry must specify a `$type`" 55 + "description": "Open union used to define the record's content. Each entry must specify a $type and may be extended with other lexicons to support additional content formats." 44 56 }, 45 57 "updatedAt": { 46 58 "type": "string", ··· 74 86 "textContent": { 75 87 "type": "string", 76 88 "description": "Plaintext representation of the documents contents. Should not contain markdown or other formatting." 89 + }, 90 + "contributors": { 91 + "type": "array", 92 + "items": { 93 + "ref": "#contributor", 94 + "type": "ref" 95 + } 77 96 } 78 97 } 79 98 }, 80 99 "description": "A document record representing a published article, blog post, or other content. Documents can belong to a publication or exist independently." 100 + }, 101 + "contributor": { 102 + "type": "object", 103 + "required": [ 104 + "did" 105 + ], 106 + "properties": { 107 + "did": { 108 + "type": "string", 109 + "format": "did" 110 + }, 111 + "role": { 112 + "type": "string", 113 + "maxLength": 1000, 114 + "maxGraphemes": 100 115 + }, 116 + "displayName": { 117 + "type": "string", 118 + "maxLength": 1000, 119 + "maxGraphemes": 100 120 + } 121 + } 81 122 } 82 123 }, 83 124 "$type": "com.atproto.lexicon.schema",
+30
app/data/lexicons/graph.recommend.json
··· 1 + { 2 + "id": "site.standard.graph.recommend", 3 + "defs": { 4 + "main": { 5 + "key": "tid", 6 + "type": "record", 7 + "record": { 8 + "type": "object", 9 + "required": [ 10 + "document", 11 + "createdAt" 12 + ], 13 + "properties": { 14 + "document": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "AT-URI reference to the document record being recommended (ex: at://did:plc:abc123/site.standard.document/xyz789)." 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime" 22 + } 23 + } 24 + }, 25 + "description": "Record declaring a recommendation of a document." 26 + } 27 + }, 28 + "$type": "com.atproto.lexicon.schema", 29 + "lexicon": 1 30 + }
+5 -1
app/data/lexicons/graph.subscription.json
··· 10 10 "publication" 11 11 ], 12 12 "properties": { 13 + "createdAt": { 14 + "type": "string", 15 + "format": "datetime" 16 + }, 13 17 "publication": { 14 18 "type": "string", 15 19 "format": "at-uri", ··· 17 21 } 18 22 } 19 23 }, 20 - "description": "Record declaring a subscription to a publication" 24 + "description": "Record declaring a subscription to a publication." 21 25 } 22 26 }, 23 27 "$type": "com.atproto.lexicon.schema",
+11 -5
app/data/lexicons/publication.json
··· 14 14 "url": { 15 15 "type": "string", 16 16 "format": "uri", 17 - "description": "Base url for the publication. (ex: https://standard.site). This value will be combined with the document path to construct a full URL for the document. Avoid trailing slashes." 17 + "description": "Base publication url (ex: https://standard.site). The canonical document URL is formed by combining this value with the document path." 18 18 }, 19 19 "icon": { 20 20 "type": "blob", ··· 30 30 "description": "Name of the publication.", 31 31 "maxGraphemes": 500 32 32 }, 33 + "labels": { 34 + "refs": [ 35 + "com.atproto.label.defs#selfLabels" 36 + ], 37 + "type": "union", 38 + "description": "Self-label values for this publication. Effectively content warnings." 39 + }, 33 40 "basicTheme": { 34 41 "ref": "site.standard.theme.basic", 35 42 "type": "ref", 36 - "description": "Simplified theme for tools and apps to utilize when displaying content." 43 + "description": "Simplified publication theme for tools and apps to utilize when displaying content." 37 44 }, 38 45 "description": { 39 46 "type": "string", ··· 56 63 "showInDiscover": { 57 64 "type": "boolean", 58 65 "default": true, 59 - "description": "Decides whether the publication should appear in discovery feeds." 66 + "description": "Boolean which decides whether the publication should appear in discovery feeds." 60 67 } 61 - }, 62 - "description": "Platform-specific preferences for the publication, including discovery and visibility settings." 68 + } 63 69 } 64 70 }, 65 71 "$type": "com.atproto.lexicon.schema",
+40 -36
app/data/lexicons/theme.basic.json
··· 2 2 "id": "site.standard.theme.basic", 3 3 "defs": { 4 4 "main": { 5 - "type": "object", 6 - "required": [ 7 - "background", 8 - "foreground", 9 - "accent", 10 - "accentForeground" 11 - ], 12 - "properties": { 13 - "accent": { 14 - "refs": [ 15 - "site.standard.theme.color#rgb" 16 - ], 17 - "type": "union", 18 - "description": "Color used for links and button backgrounds." 19 - }, 20 - "background": { 21 - "refs": [ 22 - "site.standard.theme.color#rgb" 23 - ], 24 - "type": "union", 25 - "description": "Color used for content background." 26 - }, 27 - "foreground": { 28 - "refs": [ 29 - "site.standard.theme.color#rgb" 30 - ], 31 - "type": "union", 32 - "description": "Color used for content text." 33 - }, 34 - "accentForeground": { 35 - "refs": [ 36 - "site.standard.theme.color#rgb" 37 - ], 38 - "type": "union", 39 - "description": "Color used for button text." 5 + "key": "tid", 6 + "type": "record", 7 + "record": { 8 + "type": "object", 9 + "required": [ 10 + "background", 11 + "foreground", 12 + "accent", 13 + "accentForeground" 14 + ], 15 + "properties": { 16 + "accent": { 17 + "refs": [ 18 + "site.standard.theme.color#rgb" 19 + ], 20 + "type": "union", 21 + "description": "Color used for links and button backgrounds." 22 + }, 23 + "background": { 24 + "refs": [ 25 + "site.standard.theme.color#rgb" 26 + ], 27 + "type": "union", 28 + "description": "Color used for content background." 29 + }, 30 + "foreground": { 31 + "refs": [ 32 + "site.standard.theme.color#rgb" 33 + ], 34 + "type": "union", 35 + "description": "Color used for content text." 36 + }, 37 + "accentForeground": { 38 + "refs": [ 39 + "site.standard.theme.color#rgb" 40 + ], 41 + "type": "union", 42 + "description": "Color used for button text." 43 + } 40 44 } 41 45 }, 42 - "description": "A simplified theme definition, providing basic color customization for content display across different platforms and applications." 46 + "description": "A simplified theme definition for publications, providing basic color customization for content display across different platforms and applications." 43 47 } 44 48 }, 45 49 "$type": "com.atproto.lexicon.schema",
+1
app/docs/[...slug]/page.tsx
··· 15 15 'lexicons/publication': () => import('@/content/docs/lexicons/publication.mdx'), 16 16 'lexicons/document': () => import('@/content/docs/lexicons/document.mdx'), 17 17 'lexicons/subscription': () => import('@/content/docs/lexicons/subscription.mdx'), 18 + 'lexicons/recommend': () => import('@/content/docs/lexicons/recommend.mdx'), 18 19 'lexicons/theme': () => import('@/content/docs/lexicons/theme.mdx'), 19 20 'implementations': () => import('@/content/docs/implementations.mdx'), 20 21 'faq': () => import('@/content/docs/faq.mdx'),
+6
app/lib/lexicon.ts
··· 1 1 import publicationLexicon from "../data/lexicons/publication.json"; 2 2 import documentLexicon from "../data/lexicons/document.json"; 3 3 import subscriptionLexicon from "../data/lexicons/graph.subscription.json"; 4 + import recommendLexicon from "../data/lexicons/graph.recommend.json"; 4 5 import themeBasicLexicon from "../data/lexicons/theme.basic.json"; 5 6 import themeColorLexicon from "../data/lexicons/theme.color.json"; 7 + import authFullLexicon from "../data/lexicons/authFull.json"; 8 + import authSocialLexicon from "../data/lexicons/authSocial.json"; 6 9 import { getDescriptionOverride, getPropertyOrder } from "../data/lexicon-overrides"; 7 10 8 11 export interface LexiconProperty { ··· 50 53 "site.standard.publication": publicationLexicon as LexiconSchema, 51 54 "site.standard.document": documentLexicon as LexiconSchema, 52 55 "site.standard.graph.subscription": subscriptionLexicon as LexiconSchema, 56 + "site.standard.graph.recommend": recommendLexicon as LexiconSchema, 53 57 "site.standard.theme.basic": themeBasicLexicon as LexiconSchema, 54 58 "site.standard.theme.color": themeColorLexicon as LexiconSchema, 59 + "site.standard.authFull": authFullLexicon as LexiconSchema, 60 + "site.standard.authSocial": authSocialLexicon as LexiconSchema, 55 61 }; 56 62 57 63 export function getLexicon(nsid: string): LexiconSchema | null {
+5 -1
content/docs/introduction.mdx
··· 6 6 ogImage: opengraph-image-docs-introduction-v2.png 7 7 --- 8 8 9 - import { StandardSite } from '@/app/components/docs' 9 + import { StandardSite, Updated } from '@/app/components/docs' 10 10 11 11 # Introduction 12 12 ··· 31 31 ### Subscriptions 32 32 33 33 The [`site.standard.graph.subscription`](/docs/lexicons/subscription) lexicon tracks relationships between users and publications, enabling follow functionality and personalized content feeds across the AT Protocol network. 34 + 35 + ### Recommendations <Updated label="New" /> 36 + 37 + The [`site.standard.graph.recommend`](/docs/lexicons/recommend) lexicon lets users endorse individual documents, providing a lightweight signal that aggregators and readers can use to surface trusted or popular content. 34 38 35 39 ## Design Philosophy 36 40
+21 -5
content/docs/lexicons/document.mdx
··· 6 6 ogImage: opengraph-image-docs-document-lexicon-v2.png 7 7 --- 8 8 9 - import { Table } from '@/app/components/docs' 9 + import { Table, Updated } from '@/app/components/docs' 10 10 11 11 # Document Lexicon 12 12 ··· 29 29 ]} 30 30 /> 31 31 32 - ### Optional Properties 32 + ### Optional Properties <Updated /> 33 33 34 34 <Table 35 35 headers={['Property', 'Type', 'Description']} 36 36 rows={[ 37 - ['path', 'string', <>Combine with <code>site</code> or publication <code>url</code> to construct a canonical URL to the document. A slash should be included at the beginning of this value.</>], 37 + ['path', 'string', <>Combine with <code>site</code> or publication <code>url</code> to construct a canonical URL to the document. Prepend with a leading slash.</>], 38 38 ['description', 'string', <>A brief description or excerpt from the document. <code>maxLength: 30000</code> <code>maxGraphemes: 3000</code></>], 39 39 ['coverImage', 'blob', 'Image to used for thumbnail or cover image. Less than 1MB is size.'], 40 - ['content', 'union', <>Open union used to define the record's content. Each entry must specify a <code>$type</code></>], 40 + ['content', 'union', <>Open union used to define the record's content. Each entry must specify a <code>$type</code> and may be extended with other lexicons to support additional content formats.</>], 41 41 ['textContent', 'string', 'Plaintext representation of the documents contents. Should not contain markdown or other formatting.'], 42 42 ['bskyPostRef', 'ref', 'Strong reference to a Bluesky post. Useful to keep track of comments off-platform.'], 43 - ['tags', 'array', <>Array of strings used to tag or categorize the document. Avoid prepending tags with hashtags. <code>maxLength: 1280</code> <code>maxGraphemes: 128</code></>], 43 + ['tags', 'array', <>Array of strings used to tag or categorize the document. Avoid prepending tags with hashtags. <code>items.maxLength: 1280</code> <code>items.maxGraphemes: 128</code></>], 44 + ['links', 'union', "Open union describing relationships between this document and external resources."], 45 + ['labels', 'union', <>Self-label values for this document. Effectively content warnings. (ref → <code>com.atproto.label.defs#selfLabels</code>)</>], 46 + ['contributors', 'array', <>List of additional contributors to this document (ref → <a href="#contributor">#contributor</a>).</>], 44 47 ['updatedAt', 'datetime', 'Timestamp of the documents last edit.'], 48 + ]} 49 + /> 50 + 51 + ### Contributor <Updated label="New" /> 52 + 53 + The `#contributor` property describes a participant on a document beyond the record's author. 54 + 55 + <Table 56 + headers={['Property', 'Type', 'Required', 'Description']} 57 + rows={[ 58 + ['did', 'string:did', 'Yes', 'DID of the contributor.'], 59 + ['role', 'string', 'No', <>Role of the contributor (ex: "editor", "translator"). <code>maxLength: 1000</code> <code>maxGraphemes: 100</code></>], 60 + ['displayName', 'string', 'No', <>Optional display name override for the contributor. <code>maxLength: 1000</code> <code>maxGraphemes: 100</code></>], 45 61 ]} 46 62 /> 47 63
+3 -2
content/docs/lexicons/publication.mdx
··· 6 6 ogImage: opengraph-image-docs-publication-lexicon-v2.png 7 7 --- 8 8 9 - import { Table } from '@/app/components/docs' 9 + import { Table, Updated } from '@/app/components/docs' 10 10 11 11 # Publication Lexicon 12 12 ··· 30 30 ]} 31 31 /> 32 32 33 - ### Optional Properties 33 + ### Optional Properties <Updated /> 34 34 35 35 <Table 36 36 headers={['Property', 'Type', 'Description']} ··· 38 38 ['icon', 'blob', 'Square image to identify the publication. Should be at least 256x256.'], 39 39 ['description', 'string', <>Brief description of the publication. <code>maxLength: 30000</code> <code>maxGraphemes: 3000</code></>], 40 40 ['basicTheme', 'ref', <>Simplified theme for tools and apps to utilize when displaying content. (ref → <a href="/docs/lexicons/theme">site.standard.theme.basic</a>)</>], 41 + ['labels', 'union', <>Self-label values for this publication. Effectively content warnings. (ref → <code>com.atproto.label.defs#selfLabels</code>)</>], 41 42 ['preferences', 'object', 'Platform-specific preferences for the publication, including discovery and visibility settings.'], 42 43 ['preferences.showInDiscover', 'boolean', 'Decides whether the publication should appear in discovery feeds.'], 43 44 ]}
+48
content/docs/lexicons/recommend.mdx
··· 1 + --- 2 + title: Recommend Lexicon 3 + description: Schema reference for document recommendations, used to declare that a user endorses or recommends a document. 4 + date: 2026-05-19 5 + ogImage: opengraph-image-docs-recommend-lexicon-v2.png 6 + --- 7 + 8 + import { Table, Updated } from '@/app/components/docs' 9 + 10 + # Recommend Lexicon <Updated label="New" /> 11 + 12 + The `site.standard.graph.recommend` lexicon declares that a user recommends a document. 13 + 14 + ## Overview 15 + 16 + Recommendations are lightweight social signals: a user creates a `recommend` record on their PDS pointing at a `site.standard.document` they endorse. Aggregators and readers can use these records to surface popular or trusted documents. 17 + 18 + ## Schema 19 + 20 + ### Required Properties 21 + 22 + <Table 23 + headers={['Property', 'Type', 'Description']} 24 + rows={[ 25 + ['document', 'at-uri', 'AT-URI reference to the document record being recommended (ex: at://did:plc:abc123/site.standard.document/xyz789).'], 26 + ['createdAt', 'datetime', 'Timestamp marking when the recommendation was created.'], 27 + ]} 28 + /> 29 + 30 + ## Example 31 + 32 + ```json 33 + { 34 + "$type": "site.standard.graph.recommend", 35 + "document": "at://did:plc:abc123/site.standard.document/3mbfqhezge25u", 36 + "createdAt": "2026-05-19T14:30:00.000Z" 37 + } 38 + ``` 39 + 40 + ## View the Lexicon 41 + 42 + - [View full lexicon schema](https://pdsls.dev/at://did:plc:re3ebnp5v7ffagz6rb6xfei4/com.atproto.lexicon.schema/site.standard.graph.recommend) 43 + 44 + ## Related 45 + 46 + - [Document lexicon](/docs/lexicons/document) - Documents that can be recommended 47 + - [Subscription lexicon](/docs/lexicons/subscription) - Following whole publications 48 + - [Permissions](/docs/permissions) - `site.standard.authSocial` grants access to recommend records
+13 -3
content/docs/lexicons/subscription.mdx
··· 6 6 ogImage: opengraph-image-docs-subscription-lexicon-v2.png 7 7 --- 8 8 9 - import { Table } from '@/app/components/docs' 9 + import { Table, Updated } from '@/app/components/docs' 10 10 11 11 # Subscription Lexicon 12 12 ··· 27 27 ]} 28 28 /> 29 29 30 - ## Example 30 + ### Optional Properties <Updated label="New" /> 31 + 32 + <Table 33 + headers={['Property', 'Type', 'Description']} 34 + rows={[ 35 + ['createdAt', 'datetime', 'Timestamp marking when the subscription was created.'], 36 + ]} 37 + /> 38 + 39 + ## Example <Updated /> 31 40 32 41 ```json 33 42 { 34 43 "$type": "site.standard.graph.subscription", 35 - "publication": "at://did:plc:abc123/site.standard.publication/3lwafzkjqm25s" 44 + "publication": "at://did:plc:abc123/site.standard.publication/3lwafzkjqm25s", 45 + "createdAt": "2026-05-19T14:30:00.000Z" 36 46 } 37 47 ``` 38 48
+1 -1
content/docs/lexicons/theme.mdx
··· 6 6 ogImage: opengraph-image-docs-basic-theme-lexicon-v2.png 7 7 --- 8 8 9 - import { Table } from '@/app/components/docs' 9 + import { Table, Updated } from '@/app/components/docs' 10 10 11 11 # Theme Lexicon 12 12
+27 -3
content/docs/permissions.mdx
··· 6 6 ogImage: opengraph-image-docs-permissions-v2.png 7 7 --- 8 8 9 - import { Table } from '@/app/components/docs' 9 + import { Table, Updated } from '@/app/components/docs' 10 10 import { StandardSite } from '@/app/components/docs' 11 11 12 12 # Permissions 13 13 14 - <StandardSite /> provides a permission set for applications to access publications, documents, and subscriptions. 14 + <StandardSite /> provides permission sets for applications to access publications, documents, subscriptions, and recommendations. 15 15 16 16 ## Overview 17 17 ··· 29 29 include:site.standard.authFull 30 30 ``` 31 31 32 - ### Granted Permissions 32 + ### Granted Permissions <Updated /> 33 33 34 34 This permission set grants access to the following collections: 35 35 ··· 39 39 ['site.standard.publication', 'Create, update, and delete publication records'], 40 40 ['site.standard.document', 'Create, update, and delete document records'], 41 41 ['site.standard.graph.subscription', 'Create, update, and delete subscription records'], 42 + ['site.standard.graph.recommend', 'Create, update, and delete recommendation records'], 43 + ]} 44 + /> 45 + 46 + ## Social <StandardSite /> Access <Updated label="New" /> 47 + 48 + The `site.standard.authSocial` permission set is a narrower scope for managing subscriptions and document recommendations. 49 + 50 + ### Requesting Permissions 51 + 52 + Include `site.standard.authSocial` in the OAuth scope when requesting user authorization: 53 + 54 + ``` 55 + include:site.standard.authSocial 56 + ``` 57 + 58 + ### Granted Permissions 59 + 60 + <Table 61 + headers={['Collection', 'Access scopes']} 62 + rows={[ 63 + ['site.standard.graph.subscription', 'Create, update, and delete subscription records'], 64 + ['site.standard.graph.recommend', 'Create, update, and delete recommendation records'], 42 65 ]} 43 66 /> 44 67 ··· 48 71 - [Publication lexicon](/docs/lexicons/publication) - Understanding publication records 49 72 - [Document lexicon](/docs/lexicons/document) - Understanding document records 50 73 - [Subscription lexicon](/docs/lexicons/subscription) - Understanding subscription records 74 + - [Recommend lexicon](/docs/lexicons/recommend) - Understanding recommendation records
+6
content/docs/quick-start.mdx
··· 26 26 - [`site.standard.publication`](/docs/lexicons/publication) - Publication metadata 27 27 - [`site.standard.document`](/docs/lexicons/document) - Document content and metadata 28 28 - [`site.standard.graph.subscription`](/docs/lexicons/subscription) - User-publication relationships 29 + - [`site.standard.graph.recommend`](/docs/lexicons/recommend) - Document recommendations 30 + 31 + Two permission sets are available for OAuth scopes: 32 + 33 + - [`site.standard.authFull`](/docs/permissions) - Full access to publications, documents, subscriptions, and recommendations 34 + - [`site.standard.authSocial`](/docs/permissions) - Narrower access for subscriptions and recommendations only 29 35 30 36 ### 2. Create a Publication Record 31 37
+14 -3
content/docs/verification.mdx
··· 7 7 --- 8 8 9 9 10 - import { StandardSite } from '@/app/components/docs' 10 + import { StandardSite, Updated } from '@/app/components/docs' 11 11 12 12 # Verification 13 13 ··· 35 35 at://did:plc:abc123/site.standard.publication/rkey 36 36 ``` 37 37 38 + ### Discovery Hint <Updated label="New" /> 39 + 40 + Publications may also expose their AT-URI via a `<link>` tag in the page `<head>` to encourage discovery. 41 + 42 + ```html 43 + <link 44 + rel="site.standard.publication" 45 + href="at://did:plc:abc123/site.standard.publication/rkey" 46 + /> 47 + ``` 48 + 49 + This is a hint only. Do not rely on it for verification, and always confirm via the `.well-known` endpoint. 50 + 38 51 ### Non-root Publications 39 52 40 53 If the publication does not live at the domain root, append the publication path to the endpoint: ··· 46 59 ## Document Verification 47 60 48 61 To verify an individual document, include a `<link>` tag in the document's `<head>` that references its AT-URI. 49 - 50 - ### HTML Example 51 62 52 63 ```html 53 64 <link
+18 -4
mdx-components.tsx
··· 1 1 import type { MDXComponents } from 'mdx/types' 2 + import { isValidElement } from 'react' 2 3 import Link from 'next/link' 3 4 import { ClickableHeading } from '@/app/components/docs/ClickableHeading' 4 5 import { DocsPageMenu } from '@/app/components/docs/DocsPageMenu' 6 + import { Updated } from '@/app/components/docs/Updated' 7 + 8 + function extractHeadingText(node: React.ReactNode): string { 9 + if (typeof node === 'string') return node 10 + if (typeof node === 'number') return String(node) 11 + if (Array.isArray(node)) return node.map(extractHeadingText).join('') 12 + if (isValidElement(node)) { 13 + if (node.type === Updated) return '' 14 + const props = node.props as { children?: React.ReactNode } 15 + return extractHeadingText(props.children) 16 + } 17 + return '' 18 + } 5 19 6 20 export function useMDXComponents(components: MDXComponents): MDXComponents { 7 21 return { 8 22 h1: ({ children }) => ( 9 23 <div className="flex items-start justify-between gap-2"> 10 - <ClickableHeading level={1}>{children}</ClickableHeading> 24 + <ClickableHeading level={1} text={extractHeadingText(children)}>{children}</ClickableHeading> 11 25 <DocsPageMenu /> 12 26 </div> 13 27 ), 14 28 h2: ({ children }) => ( 15 - <ClickableHeading level={2}>{children}</ClickableHeading> 29 + <ClickableHeading level={2} text={extractHeadingText(children)}>{children}</ClickableHeading> 16 30 ), 17 31 h3: ({ children }) => ( 18 - <ClickableHeading level={3}>{children}</ClickableHeading> 32 + <ClickableHeading level={3} text={extractHeadingText(children)}>{children}</ClickableHeading> 19 33 ), 20 34 h4: ({ children }) => ( 21 - <ClickableHeading level={4}>{children}</ClickableHeading> 35 + <ClickableHeading level={4} text={extractHeadingText(children)}>{children}</ClickableHeading> 22 36 ), 23 37 p: ({ children }) => ( 24 38 <p className="text-base sm:text-lg leading-relaxed tracking-tight text-muted mb-4">
+9 -1
public/llms.txt
··· 28 28 - Subscription: https://standard.site/docs/lexicons/subscription 29 29 `site.standard.graph.subscription` - Follow relationships 30 30 31 + - Recommend: https://standard.site/docs/lexicons/recommend 32 + `site.standard.graph.recommend` - Document endorsements 33 + 31 34 - Theme: https://standard.site/docs/lexicons/theme 32 - `site.standard.theme` - Theming configuration 35 + `site.standard.theme.basic` - Publication theming (colors) 33 36 34 37 ### Resources 35 38 - Implementations: https://standard.site/docs/implementations ··· 53 56 - Lexicons: Schemas that define record types in AT Protocol 54 57 - NSID: Namespaced Identifier - unique identifier format for lexicons 55 58 59 + ## Permission Sets 60 + - site.standard.authFull: Full access to publications, documents, subscriptions, and recommends 61 + - site.standard.authSocial: Narrower scope for managing subscriptions and recommends only 62 + 56 63 ## Implementation Quick Reference 57 64 1. Create publication record with site.standard.publication lexicon 58 65 2. Create document records with site.standard.document lexicon 59 66 3. Link documents to publication via publication reference 60 67 4. Implement verification to link records to your domain 61 68 5. Support subscription records for follow functionality 69 + 6. Support recommend records for document endorsements
+3
scripts/sync-lexicons.ts
··· 12 12 "site.standard.theme.basic", 13 13 "site.standard.theme.color", 14 14 "site.standard.graph.subscription", 15 + "site.standard.graph.recommend", 16 + "site.standard.authFull", 17 + "site.standard.authSocial", 15 18 ]; 16 19 17 20 const __dirname = dirname(fileURLToPath(import.meta.url));