···21432143 },
21442144 site: {
21452145 description:
21462146- 'URI to the site or publication this document belongs to (https or at-uri)',
21462146+ 'URI to the site or publication this document belongs to. Supports both AT-URIs (at://did/collection/rkey) for publication references and HTTPS URLs (https://example.com) for standalone documents or external sites.',
21472147 format: 'uri',
21482148 type: 'string',
21492149 },
···21572157 },
21582158 textContent: {
21592159 type: 'string',
21602160+ },
21612161+ theme: {
21622162+ description:
21632163+ 'Theme for standalone documents. For documents in publications, theme is inherited from the publication.',
21642164+ ref: 'lex:pub.leaflet.publication#theme',
21652165+ type: 'ref',
21602166 },
21612167 title: {
21622168 maxGraphemes: 128,
+3-1
lexicons/api/types/site/standard/document.ts
···77import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
88import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef'
99import type * as PubLeafletContent from '../../pub/leaflet/content'
1010+import type * as PubLeafletPublication from '../../pub/leaflet/publication'
10111112const is$typed = _is$typed,
1213 validate = _validate
···2122 /** combine with the publication url or the document site to construct a full url to the document */
2223 path?: string
2324 publishedAt: string
2424- /** URI to the site or publication this document belongs to (https or at-uri) */
2525+ /** URI to the site or publication this document belongs to. Supports both AT-URIs (at://did/collection/rkey) for publication references and HTTPS URLs (https://example.com) for standalone documents or external sites. */
2526 site: string
2627 tags?: string[]
2728 textContent?: string
2929+ theme?: PubLeafletPublication.Theme
2830 title: string
2931 updatedAt?: string
3032 [k: string]: unknown
+6-1
lexicons/site/standard/document.json
···3232 "type": "string"
3333 },
3434 "site": {
3535- "description": "URI to the site or publication this document belongs to (https or at-uri)",
3535+ "description": "URI to the site or publication this document belongs to. Supports both AT-URIs (at://did/collection/rkey) for publication references and HTTPS URLs (https://example.com) for standalone documents or external sites.",
3636 "format": "uri",
3737 "type": "string"
3838 },
···4646 },
4747 "textContent": {
4848 "type": "string"
4949+ },
5050+ "theme": {
5151+ "description": "Theme for standalone documents. For documents in publications, theme is inherited from the publication.",
5252+ "ref": "pub.leaflet.publication#theme",
5353+ "type": "ref"
4954 },
5055 "title": {
5156 "maxGraphemes": 128,
+18-4
lexicons/src/normalize.ts
···33 *
44 * The standard format (site.standard.*) is used as the canonical representation for
55 * reading data from the database, while both formats are accepted for storage.
66+ *
77+ * ## Site Field Format
88+ *
99+ * The `site` field in site.standard.document supports two URI formats:
1010+ * - AT-URIs (at://did/collection/rkey) - Used when document belongs to an AT Protocol publication
1111+ * - HTTPS URLs (https://example.com) - Used for standalone documents or external sites
1212+ *
1313+ * Both formats are valid and should be handled by consumers.
614 */
715816import type * as PubLeafletDocument from "../api/types/pub/leaflet/document";
···140148export function normalizeDocument(record: unknown): NormalizedDocument | null {
141149 if (!record || typeof record !== "object") return null;
142150143143- // Pass through site.standard records directly
151151+ // Pass through site.standard records directly (theme is already in correct format if present)
144152 if (isStandardDocument(record)) {
145145- return record as NormalizedDocument;
153153+ return {
154154+ ...record,
155155+ theme: record.theme,
156156+ } as NormalizedDocument;
146157 }
147158148159 if (isLeafletDocument(record)) {
149160 // Convert from pub.leaflet to site.standard
150150- const site = record.publication;
151161 const publishedAt = record.publishedAt;
152162153153- if (!site || !publishedAt) {
163163+ if (!publishedAt) {
154164 return null;
155165 }
166166+167167+ // For standalone documents (no publication), construct a site URL from the author
168168+ // This matches the pattern used in publishToPublication.ts for new standalone docs
169169+ const site = record.publication || `https://leaflet.pub/p/${record.author}`;
156170157171 // Wrap pages in pub.leaflet.content structure
158172 const content: $Typed<PubLeafletContent.Main> | undefined = record.pages