@singi-labs/sifa-sdk 0.18.11 → 0.18.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/index.cjs +29 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +28 -3
  4. package/dist/index.d.ts +28 -3
  5. package/dist/index.js +28 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/{network-map-CMod-9od.d.ts → network-map-5WbfyFed.d.ts} +12 -3
  8. package/dist/{network-map-CH37aj_Q.d.cts → network-map-Cwv7SsL0.d.cts} +12 -3
  9. package/dist/{org-3gFPQ_pC.d.cts → org-BHz9ymjp.d.cts} +122 -4
  10. package/dist/{org-CTSJ5zTZ.d.ts → org-Bgz5parA.d.ts} +122 -4
  11. package/dist/{profile-investment-CmiQxk5C.d.cts → profile-investment-CIG7Hy8g.d.cts} +16 -1
  12. package/dist/{profile-investment-CmiQxk5C.d.ts → profile-investment-CIG7Hy8g.d.ts} +16 -1
  13. package/dist/publishing/index.cjs +5 -0
  14. package/dist/publishing/index.cjs.map +1 -1
  15. package/dist/publishing/index.js +5 -0
  16. package/dist/publishing/index.js.map +1 -1
  17. package/dist/query/fetchers/index.cjs +121 -5
  18. package/dist/query/fetchers/index.cjs.map +1 -1
  19. package/dist/query/fetchers/index.d.cts +3 -3
  20. package/dist/query/fetchers/index.d.ts +3 -3
  21. package/dist/query/fetchers/index.js +118 -6
  22. package/dist/query/fetchers/index.js.map +1 -1
  23. package/dist/query/hooks/index.cjs +157 -4
  24. package/dist/query/hooks/index.cjs.map +1 -1
  25. package/dist/query/hooks/index.d.cts +3 -3
  26. package/dist/query/hooks/index.d.ts +3 -3
  27. package/dist/query/hooks/index.js +155 -5
  28. package/dist/query/hooks/index.js.map +1 -1
  29. package/dist/query/index.cjs +161 -5
  30. package/dist/query/index.cjs.map +1 -1
  31. package/dist/query/index.d.cts +5 -5
  32. package/dist/query/index.d.ts +5 -5
  33. package/dist/query/index.js +155 -6
  34. package/dist/query/index.js.map +1 -1
  35. package/dist/schemas/index.cjs +6 -0
  36. package/dist/schemas/index.cjs.map +1 -1
  37. package/dist/schemas/index.d.cts +1 -1
  38. package/dist/schemas/index.d.ts +1 -1
  39. package/dist/schemas/index.js +6 -1
  40. package/dist/schemas/index.js.map +1 -1
  41. package/dist/schemas/write/index.cjs +7 -2
  42. package/dist/schemas/write/index.cjs.map +1 -1
  43. package/dist/schemas/write/index.js +7 -2
  44. package/dist/schemas/write/index.js.map +1 -1
  45. package/dist/{use-org-notification-emails-J29DcsSu.d.ts → use-org-notification-emails-C9CWyhSC.d.ts} +24 -2
  46. package/dist/{use-org-notification-emails-CdRgTAj4.d.cts → use-org-notification-emails-vNekuQKF.d.cts} +24 -2
  47. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/schemas/shared.ts","../../src/publishing/schemas.ts","../../src/publishing/registry.ts","../../src/publishing/linkblog.ts","../../src/publishing/types.ts"],"names":["z"],"mappings":"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","file":"index.cjs","sourcesContent":["import { z } from 'zod';\n\n/**\n * Grapheme-aware refinement matching the AT Protocol lexicon `maxGraphemes`\n * constraint. JS strings are sequences of UTF-16 code units, but lexicon\n * `maxGraphemes` counts user-perceived characters (grapheme clusters), so\n * emoji sequences, regional indicators, ZWJ joins, and combining marks all\n * count as one unit each. We use `Intl.Segmenter` to enforce this correctly.\n */\nexport function maxGraphemes(max: number) {\n return (value: string): boolean => {\n const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n let count = 0;\n for (const _ of segmenter.segment(value)) {\n count++;\n if (count > max) return false;\n }\n return true;\n };\n}\n\n/** Decentralized identifier, AT Protocol `format: did`. */\nexport const didSchema = z.string().regex(/^did:[a-z]+:[a-zA-Z0-9._:%-]+$/, 'Invalid DID');\n\n/** RFC 3339 datetime with timezone offset, AT Protocol `format: datetime`. */\nexport const datetimeSchema = z.string().datetime({ offset: true });\n\n/**\n * Freeform calendar date for user-facing profile dates (start/end, issued,\n * completed, awarded, published, etc.). Accepts a bare year, year-month,\n * year-month-day, or a full datetime (so a legacy record's RFC-3339 date maps\n * through on backfill without loss). NOT strict `datetime` -- users typically\n * remember only month/year, and LinkedIn-importer writes carry partial dates,\n * so the lexicons document these as freeform `YYYY-MM` / `YYYY-MM-DD`. Record\n * metadata like `createdAt` stays on `datetimeSchema`.\n */\nexport const partialDateSchema = z\n .string()\n .regex(/^\\d{4}(-\\d{2}(-\\d{2}(T.+)?)?)?$/, 'Expected YYYY, YYYY-MM, YYYY-MM-DD, or a datetime');\n\n/** Generic AT-URI, AT Protocol `format: at-uri`. */\nexport const atUriSchema = z.string().regex(/^at:\\/\\/[^\\s]+$/, 'Invalid AT-URI');\n\n/** Content identifier, AT Protocol `format: cid`. Loose validation -- accepts CIDv0 and CIDv1. */\nexport const cidSchema = z\n .string()\n .regex(/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|b[A-Za-z0-9+/=]+)$/, 'Invalid CID');\n\n/** BCP 47 language tag, AT Protocol `format: language`. */\nexport const languageTagSchema = z\n .string()\n .regex(/^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/, 'Invalid BCP 47 language tag');\n\n/** Generic URI, AT Protocol `format: uri`. */\nexport const uriSchema = z.string().url();\n\n/**\n * StrongRef shape from `com.atproto.repo.strongRef` -- pins a record by both\n * AT-URI (identity) and CID (version).\n */\nexport const strongRefSchema = z.object({\n uri: atUriSchema,\n cid: cidSchema,\n});\n\n/**\n * Reference to an `id.sifa.profile.skill` record by AT-URI. Mirrors\n * `id.sifa.defs#skillRef`, which carries no CID at all: skills are mutable\n * records in the same user's repo, so the reference resolves live and tracks\n * edits to the target.\n */\nexport const skillRefSchema = z.object({\n uri: atUriSchema,\n});\n\n/**\n * Reference to a record by AT-URI, with an optional CID. Mirrors\n * `id.sifa.defs#externalRecordRef`. Unlike a strongRef the CID is optional:\n * consumers resolve the AT-URI live so the reference tracks edits to the\n * target, and the CID is a best-effort integrity hint only.\n */\nexport const externalRecordRefSchema = z.object({\n uri: atUriSchema,\n cid: cidSchema.optional(),\n});\n\n/**\n * Self-labels shape from `com.atproto.label.defs#selfLabels`. Modelled\n * permissively because clients rarely construct this directly; the AppView\n * handles label validation.\n */\nexport const selfLabelsSchema = z.object({\n $type: z.literal('com.atproto.label.defs#selfLabels').optional(),\n values: z.array(z.object({ val: z.string() })),\n});\n","import { z } from 'zod';\n\nimport {\n atUriSchema,\n datetimeSchema,\n didSchema,\n maxGraphemes,\n selfLabelsSchema,\n strongRefSchema,\n uriSchema,\n} from '../schemas/shared.js';\n\n/**\n * Zod schemas mirroring the canonical Standard.site lexicons that Sifa\n * consumes when rendering publication embeds.\n *\n * Canonical lexicons live at:\n * DID: did:plc:re3ebnp5v7ffagz6rb6xfei4\n * PDS: https://auriporia.us-west.host.bsky.network\n * Collection: com.atproto.lexicon.schema\n *\n * Sifa does NOT own these lexicons; we vendor the validation shapes so\n * clients can parse augmented embeds and (in Phase 4) write subscription\n * records to viewers' PDSes. Re-check against the canonical PDS on each\n * SDK release to detect schema drift.\n */\n\nconst rgbColorSchema = z.object({\n r: z.number().int().min(0).max(255),\n g: z.number().int().min(0).max(255),\n b: z.number().int().min(0).max(255),\n});\n\nexport type RgbColor = z.infer<typeof rgbColorSchema>;\n\n/** site.standard.theme.basic */\nexport const BasicThemeSchema = z.object({\n background: rgbColorSchema,\n foreground: rgbColorSchema,\n accent: rgbColorSchema,\n accentForeground: rgbColorSchema,\n});\n\nexport type BasicTheme = z.infer<typeof BasicThemeSchema>;\n\nconst blobRefSchema = z\n .object({\n $type: z.string().optional(),\n ref: z.unknown(),\n mimeType: z.string().optional(),\n size: z.number().optional(),\n })\n .passthrough();\n\n/** site.standard.publication */\nexport const StandardSitePublicationRecordSchema = z\n .object({\n url: uriSchema,\n name: z.string().min(1).refine(maxGraphemes(500)).max(5000),\n description: z.string().refine(maxGraphemes(3000)).max(30000).optional(),\n icon: blobRefSchema.optional(),\n basicTheme: BasicThemeSchema.optional(),\n labels: selfLabelsSchema.optional(),\n preferences: z\n .object({\n showInDiscover: z.boolean().optional(),\n })\n .partial()\n .optional(),\n })\n .passthrough();\n\nexport type StandardSitePublicationRecord = z.infer<typeof StandardSitePublicationRecordSchema>;\n\nconst contributorSchema = z\n .object({\n did: didSchema,\n role: z.string().refine(maxGraphemes(100)).max(1000).optional(),\n displayName: z.string().refine(maxGraphemes(100)).max(1000).optional(),\n })\n .passthrough();\n\n/** site.standard.document */\nexport const StandardSiteDocumentRecordSchema = z\n .object({\n site: z.string().min(1),\n title: z.string().min(1).refine(maxGraphemes(500)).max(5000),\n path: z.string().optional(),\n publishedAt: datetimeSchema,\n updatedAt: datetimeSchema.optional(),\n description: z.string().refine(maxGraphemes(3000)).max(30000).optional(),\n textContent: z.string().optional(),\n tags: z.array(z.string().refine(maxGraphemes(128)).max(1280)).optional(),\n coverImage: blobRefSchema.optional(),\n contributors: z.array(contributorSchema).optional(),\n bskyPostRef: strongRefSchema.optional(),\n labels: selfLabelsSchema.optional(),\n })\n .passthrough();\n\nexport type StandardSiteDocumentRecord = z.infer<typeof StandardSiteDocumentRecordSchema>;\n\n/**\n * site.standard.graph.subscription\n *\n * Authored by a viewer to declare a subscription to a publication. Used\n * by Phase 4 inline-subscribe path. Record key is a TID; the AT-URI\n * shape is `at://<viewer-did>/site.standard.graph.subscription/<rkey>`.\n */\nexport const StandardSiteSubscriptionRecordSchema = z.object({\n publication: atUriSchema,\n createdAt: datetimeSchema.optional(),\n});\n\nexport type StandardSiteSubscriptionRecord = z.infer<typeof StandardSiteSubscriptionRecordSchema>;\n\n/**\n * site.standard.graph.recommend\n *\n * Document-level \"like\" record. Same scope as subscription via the\n * site.standard.authSocial OAuth permission-set, so Phase 4 unlocks\n * inline likes alongside inline subscribes.\n */\nexport const StandardSiteRecommendRecordSchema = z.object({\n document: atUriSchema,\n createdAt: datetimeSchema,\n});\n\nexport type StandardSiteRecommendRecord = z.infer<typeof StandardSiteRecommendRecordSchema>;\n","/**\n * Publisher allowlist for the Standard.site rich embed.\n *\n * Mirrors `bluesky-social/social-app`\n * `src/components/Post/Embed/StandardSiteEmbed/publishers.ts`. The only\n * gate this provides is which publishers get the highlighted\n * \"Subscribe on {Publisher}\" CTA with a publisher icon vs the plain\n * \"View publication\" fallback — any Standard.site embed renders\n * regardless of allowlist membership.\n *\n * Sync policy: review additions against the upstream bsky list weekly;\n * Singi Labs reviews before merge.\n */\n\nexport interface Publisher {\n /** Lowercase host. Subdomains are matched via `hostMatches`. */\n host: string;\n /** Human-facing publisher name shown in CTA copy. */\n name: string;\n /**\n * Stable identifier used by clients to look up brand icons. The SDK\n * does not ship icon assets — sifa-web maintains them keyed by this\n * id so React Native vs web rendering can diverge.\n */\n iconKey: 'leaflet' | 'pckt' | 'offprint';\n}\n\nexport const STANDARD_SITE_PUBLISHERS: readonly Publisher[] = Object.freeze([\n { host: 'leaflet.pub', name: 'Leaflet', iconKey: 'leaflet' },\n { host: 'pckt.blog', name: 'pckt', iconKey: 'pckt' },\n { host: 'offprint.app', name: 'Offprint', iconKey: 'offprint' },\n]);\n\n/**\n * Returns true when `host` exactly matches `target` or is a subdomain of\n * it. Hosts are expected to be lowercase already.\n */\nexport function hostMatches(host: string, target: string): boolean {\n return host === target || host.endsWith('.' + target);\n}\n\nfunction hostFromUri(uri: string | undefined | null): string | null {\n if (!uri) return null;\n try {\n return new URL(uri).host.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * Match a publisher by host. Returns the publisher when `host` is on the\n * allowlist (exact or subdomain match), null otherwise. Host should be\n * lowercase.\n */\nexport function matchPublisherByHost(host: string | null | undefined): Publisher | null {\n if (!host) return null;\n const lower = host.toLowerCase();\n return STANDARD_SITE_PUBLISHERS.find((p) => hostMatches(lower, p.host)) ?? null;\n}\n\n/**\n * Match a publisher by URI. Convenience wrapper around\n * `matchPublisherByHost` for callers that have a publication URL handy.\n */\nexport function matchPublisherByUri(uri: string | undefined | null): Publisher | null {\n return matchPublisherByHost(hostFromUri(uri));\n}\n","/**\n * Linkblog-share detection for the Standard.site document family.\n *\n * A \"linkblog\" (e.g. Skyreader) writes a `site.standard.document` to the\n * user's PDS for every *external* article they share. Those documents are\n * valid Standard.site posts, so they surface in Sifa's activity stream and\n * profile portfolio as if the user authored them — but the user only shared\n * someone else's writing (sifa-workspace#351).\n *\n * These predicates identify such shares so callers can exclude them, while\n * leaving genuinely authored Standard.site posts (Leaflet, pckt, Offprint\n * essays) untouched.\n *\n * Signals, most to least reliable:\n * 1. The Skyreader provenance marker on the document (`skyreaderLinkblog`).\n * 2. The document's parent publication is itself a linkblog (the marker\n * lives on the publication even when older documents lack their own).\n * Callers resolve the publication and pass `publicationIsLinkblog`.\n * 3. A `links` entry with `rel: \"repost\"` — a quote-reshare, inherently\n * not the user's own writing.\n * 4. Heuristic: a `links` entry with `rel: \"related\"` pointing to an\n * external host different from the document's own publication host —\n * the document's subject is an off-site article. Only applied when the\n * caller supplies the resolved `publicationHost`, so an authored post\n * that merely cites an external resource is never hidden on a guess.\n */\n\n/**\n * Discovery marker Skyreader stamps on every linkblog publication and on\n * link-share documents. Value is a fixed URL, not a per-user value.\n */\nexport const SKYREADER_LINKBLOG_MARKER_URL = 'https://skyreader.app/linkblog';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** A non-empty `skyreaderLinkblog` marker field. */\nfunction hasLinkblogMarker(record: Record<string, unknown>): boolean {\n const marker = record.skyreaderLinkblog;\n return typeof marker === 'string' && marker.length > 0;\n}\n\nfunction hostOf(uri: string): string | null {\n try {\n return new URL(uri).host.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * True when a `site.standard.publication` record is a linkblog rather than an\n * authored publication — i.e. it carries the Skyreader provenance marker.\n */\nexport function isLinkblogPublication(record: unknown): boolean {\n return isRecord(record) && hasLinkblogMarker(record);\n}\n\nexport interface LinkblogShareOptions {\n /**\n * True when the document's parent publication has been resolved and is\n * itself a linkblog (see {@link isLinkblogPublication}). Catches shares\n * whose document predates the per-document marker.\n */\n publicationIsLinkblog?: boolean;\n /**\n * Resolved host of the document's parent publication, enabling the\n * external-subject heuristic. Omit when the publication (hence its host)\n * could not be resolved — the heuristic then stays off.\n */\n publicationHost?: string;\n}\n\n/**\n * True when a `site.standard.document` is a shared/linkblogged external\n * article rather than the user's own writing. See the module comment for the\n * signal order.\n */\nexport function isLinkblogShareDocument(record: unknown, opts: LinkblogShareOptions = {}): boolean {\n if (!isRecord(record)) return false;\n\n // 1. Provenance marker on the document itself.\n if (hasLinkblogMarker(record)) return true;\n\n // 2. Parent publication is a linkblog (caller-resolved).\n if (opts.publicationIsLinkblog === true) return true;\n\n const links = record.links;\n if (!Array.isArray(links)) return false;\n\n const pubHost = opts.publicationHost?.toLowerCase();\n for (const link of links) {\n if (!isRecord(link) || typeof link.rel !== 'string') continue;\n\n // 3. Reshare of another record.\n if (link.rel === 'repost') return true;\n\n // 4. External-subject heuristic (only with a known publication host).\n if (link.rel === 'related' && pubHost && typeof link.uri === 'string') {\n const linkHost = hostOf(link.uri);\n if (linkHost && linkHost !== pubHost) return true;\n }\n }\n\n return false;\n}\n","import type { BasicTheme } from './schemas.js';\n\n/**\n * Publication metadata embedded in the augmented activity view by\n * sifa-api when an external link card matches an indexed publication.\n *\n * `uri` is the publication's AT-URI (e.g.\n * `at://did:plc:abc/site.standard.publication/xyz`). `theme` carries\n * the publication's own colors when supplied — clients should apply a\n * WCAG min-contrast fallback before honoring them.\n *\n * `icon` is left as `unknown` here because its representation depends\n * on the surface: blob ref on the wire, CDN URL after resolution.\n */\nexport interface PublicationSource {\n uri: string;\n title: string;\n icon?: unknown;\n theme?: BasicTheme;\n}\n\n/**\n * Shape attached to activity items whose external link card URL matches\n * a Standard.site record. Mirrors the relevant fields of bsky's\n * `StandardSiteEmbed` view so a sifa-web renderer can be near-1:1.\n *\n * `associatedRefs` carries AT-URIs the client may use in Phase 4 to\n * query subscription state for the viewer.\n */\nexport interface StandardSiteEmbedView {\n uri: string;\n source: PublicationSource;\n associatedRefs: { uri: string }[];\n createdAt?: string;\n readingTime?: number;\n}\n\nexport const STANDARD_SITE_PUBLICATION_NSID = 'site.standard.publication' as const;\nexport const STANDARD_SITE_DOCUMENT_NSID = 'site.standard.document' as const;\nexport const STANDARD_SITE_SUBSCRIPTION_NSID = 'site.standard.graph.subscription' as const;\nexport const STANDARD_SITE_RECOMMEND_NSID = 'site.standard.graph.recommend' as const;\n\n/**\n * Pre-defined OAuth permission-set the consumer apps request to write\n * subscription + recommend records on behalf of the viewer. The actual\n * permission-set is published under\n * `at://did:plc:re3ebnp5v7ffagz6rb6xfei4/com.atproto.lexicon.schema/site.standard.authSocial`.\n */\nexport const STANDARD_SITE_AUTH_SOCIAL_NSID = 'site.standard.authSocial' as const;\n\nconst COLLECTION_SET = new Set<string>([\n STANDARD_SITE_PUBLICATION_NSID,\n STANDARD_SITE_DOCUMENT_NSID,\n STANDARD_SITE_SUBSCRIPTION_NSID,\n STANDARD_SITE_RECOMMEND_NSID,\n]);\n\n/**\n * True when the AT-URI's collection segment is a Standard.site\n * collection. Useful for detecting Standard.site embed augmentation on\n * arbitrary activity items.\n */\nexport function isStandardSiteAtUri(uri: string): boolean {\n if (!uri.startsWith('at://')) return false;\n const segments = uri.slice('at://'.length).split('/');\n const collection = segments[1];\n return collection !== undefined && COLLECTION_SET.has(collection);\n}\n\n/**\n * True when any `associatedRefs[].uri` is a Standard.site collection\n * AT-URI. The bsky client uses the same check to gate the\n * `StandardSiteEmbed` renderer.\n */\nexport function hasStandardSiteAssociatedRef(refs: { uri: string }[] | undefined): boolean {\n if (!refs) return false;\n return refs.some((r) => isStandardSiteAtUri(r.uri));\n}\n"]}
1
+ {"version":3,"sources":["../../src/schemas/shared.ts","../../src/publishing/schemas.ts","../../src/publishing/registry.ts","../../src/publishing/linkblog.ts","../../src/publishing/types.ts"],"names":["z"],"mappings":";;;;;AASO,SAAS,aAAa,GAAA,EAAa;AACxC,EAAA,OAAO,CAAC,KAAA,KAA2B;AACjC,IAAA,MAAM,SAAA,GAAY,IAAI,IAAA,CAAK,SAAA,CAAU,QAAW,EAAE,WAAA,EAAa,YAAY,CAAA;AAC3E,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,KAAA,MAAW,CAAA,IAAK,SAAA,CAAU,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxC,MAAA,KAAA,EAAA;AACA,MAAA,IAAI,KAAA,GAAQ,KAAK,OAAO,KAAA;AAAA,IAC1B;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AACF;AAGO,IAAM,YAAYA,KAAA,CAAE,MAAA,EAAO,CAAE,KAAA,CAAM,kCAAkC,aAAa,CAAA;AAGlF,IAAM,cAAA,GAAiBA,MAAE,MAAA,EAAO,CAAE,SAAS,EAAE,MAAA,EAAQ,MAAM,CAAA;AAWjCA,KAAA,CAC9B,MAAA,EAAO,CACP,KAAA,CAAM,mCAAmC,mDAAmD;AAGxF,IAAM,cAAcA,KAAA,CAAE,MAAA,EAAO,CAAE,KAAA,CAAM,mBAAmB,gBAAgB,CAAA;AAGxE,IAAM,YAAYA,KAAA,CACtB,MAAA,EAAO,CACP,KAAA,CAAM,mDAAmD,aAAa,CAAA;AAGxCA,KAAA,CAC9B,MAAA,EAAO,CACP,KAAA,CAAM,uCAAuC,6BAA6B;AAGtE,IAAM,SAAA,GAAYA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,EAAI;AAMjC,IAAM,eAAA,GAAkBA,MAAE,MAAA,CAAO;AAAA,EACtC,GAAA,EAAK,WAAA;AAAA,EACL,GAAA,EAAK;AACP,CAAC,CAAA;AAQ6BA,MAAE,MAAA,CAAO;AAAA,EACrC,GAAA,EAAK;AACP,CAAC;AAQsCA,MAAE,MAAA,CAAO;AAAA,EAC9C,GAAA,EAAK,WAAA;AAAA,EACL,GAAA,EAAK,UAAU,QAAA;AACjB,CAAC;AAW6BA,MAC3B,MAAA,CAAO;AAAA,EACN,IAAA,EAAMA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,CAAI,CAAC,CAAA,CAAE,MAAA,CAAO,YAAA,CAAa,GAAG,CAAC,CAAA,CAAE,IAAI,IAAI,CAAA;AAAA,EAC1D,GAAA,EAAK,UAAU,QAAA,EAAS;AAAA,EACxB,SAAA,EAAW,UAAU,QAAA;AACvB,CAAC,EACA,WAAA;AASI,IAAM,gBAAA,GAAmBA,MAAE,MAAA,CAAO;AAAA,EACvC,KAAA,EAAOA,KAAA,CAAE,OAAA,CAAQ,mCAAmC,EAAE,QAAA,EAAS;AAAA,EAC/D,MAAA,EAAQA,KAAA,CAAE,KAAA,CAAMA,KAAA,CAAE,MAAA,CAAO,EAAE,GAAA,EAAKA,KAAA,CAAE,MAAA,EAAO,EAAG,CAAC;AAC/C,CAAC,CAAA;;;ACtFD,IAAM,cAAA,GAAiBA,MAAE,MAAA,CAAO;AAAA,EAC9B,CAAA,EAAGA,KAAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,GAAG,CAAA;AAAA,EAClC,CAAA,EAAGA,KAAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,GAAG,CAAA;AAAA,EAClC,CAAA,EAAGA,KAAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,GAAG;AACpC,CAAC,CAAA;AAKM,IAAM,gBAAA,GAAmBA,MAAE,MAAA,CAAO;AAAA,EACvC,UAAA,EAAY,cAAA;AAAA,EACZ,UAAA,EAAY,cAAA;AAAA,EACZ,MAAA,EAAQ,cAAA;AAAA,EACR,gBAAA,EAAkB;AACpB,CAAC;AAID,IAAM,aAAA,GAAgBA,MACnB,MAAA,CAAO;AAAA,EACN,KAAA,EAAOA,KAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC3B,GAAA,EAAKA,MAAE,OAAA,EAAQ;AAAA,EACf,QAAA,EAAUA,KAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC9B,IAAA,EAAMA,KAAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AACnB,CAAC,EACA,WAAA,EAAY;AAGR,IAAM,mCAAA,GAAsCA,MAChD,MAAA,CAAO;AAAA,EACN,GAAA,EAAK,SAAA;AAAA,EACL,IAAA,EAAMA,KAAAA,CAAE,MAAA,EAAO,CAAE,GAAA,CAAI,CAAC,CAAA,CAAE,MAAA,CAAO,YAAA,CAAa,GAAG,CAAC,CAAA,CAAE,IAAI,GAAI,CAAA;AAAA,EAC1D,WAAA,EAAaA,KAAAA,CAAE,MAAA,EAAO,CAAE,MAAA,CAAO,YAAA,CAAa,GAAI,CAAC,CAAA,CAAE,GAAA,CAAI,GAAK,CAAA,CAAE,QAAA,EAAS;AAAA,EACvE,IAAA,EAAM,cAAc,QAAA,EAAS;AAAA,EAC7B,UAAA,EAAY,iBAAiB,QAAA,EAAS;AAAA,EACtC,MAAA,EAAQ,iBAAiB,QAAA,EAAS;AAAA,EAClC,WAAA,EAAaA,MACV,MAAA,CAAO;AAAA,IACN,cAAA,EAAgBA,KAAAA,CAAE,OAAA,EAAQ,CAAE,QAAA;AAAS,GACtC,CAAA,CACA,OAAA,EAAQ,CACR,QAAA;AACL,CAAC,EACA,WAAA;AAIH,IAAM,iBAAA,GAAoBA,MACvB,MAAA,CAAO;AAAA,EACN,GAAA,EAAK,SAAA;AAAA,EACL,IAAA,EAAMA,KAAAA,CAAE,MAAA,EAAO,CAAE,MAAA,CAAO,YAAA,CAAa,GAAG,CAAC,CAAA,CAAE,GAAA,CAAI,GAAI,CAAA,CAAE,QAAA,EAAS;AAAA,EAC9D,WAAA,EAAaA,KAAAA,CAAE,MAAA,EAAO,CAAE,MAAA,CAAO,YAAA,CAAa,GAAG,CAAC,CAAA,CAAE,GAAA,CAAI,GAAI,CAAA,CAAE,QAAA;AAC9D,CAAC,EACA,WAAA,EAAY;AAGR,IAAM,gCAAA,GAAmCA,MAC7C,MAAA,CAAO;AAAA,EACN,IAAA,EAAMA,KAAAA,CAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA;AAAA,EACtB,KAAA,EAAOA,KAAAA,CAAE,MAAA,EAAO,CAAE,GAAA,CAAI,CAAC,CAAA,CAAE,MAAA,CAAO,YAAA,CAAa,GAAG,CAAC,CAAA,CAAE,IAAI,GAAI,CAAA;AAAA,EAC3D,IAAA,EAAMA,KAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC1B,WAAA,EAAa,cAAA;AAAA,EACb,SAAA,EAAW,eAAe,QAAA,EAAS;AAAA,EACnC,WAAA,EAAaA,KAAAA,CAAE,MAAA,EAAO,CAAE,MAAA,CAAO,YAAA,CAAa,GAAI,CAAC,CAAA,CAAE,GAAA,CAAI,GAAK,CAAA,CAAE,QAAA,EAAS;AAAA,EACvE,WAAA,EAAaA,KAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EACjC,IAAA,EAAMA,KAAAA,CAAE,KAAA,CAAMA,KAAAA,CAAE,QAAO,CAAE,MAAA,CAAO,YAAA,CAAa,GAAG,CAAC,CAAA,CAAE,GAAA,CAAI,IAAI,CAAC,EAAE,QAAA,EAAS;AAAA,EACvE,UAAA,EAAY,cAAc,QAAA,EAAS;AAAA,EACnC,YAAA,EAAcA,KAAAA,CAAE,KAAA,CAAM,iBAAiB,EAAE,QAAA,EAAS;AAAA,EAClD,WAAA,EAAa,gBAAgB,QAAA,EAAS;AAAA,EACtC,MAAA,EAAQ,iBAAiB,QAAA;AAC3B,CAAC,EACA,WAAA;AAWI,IAAM,oCAAA,GAAuCA,MAAE,MAAA,CAAO;AAAA,EAC3D,WAAA,EAAa,WAAA;AAAA,EACb,SAAA,EAAW,eAAe,QAAA;AAC5B,CAAC;AAWM,IAAM,iCAAA,GAAoCA,MAAE,MAAA,CAAO;AAAA,EACxD,QAAA,EAAU,WAAA;AAAA,EACV,SAAA,EAAW;AACb,CAAC;;;ACnGM,IAAM,wBAAA,GAAiD,OAAO,MAAA,CAAO;AAAA,EAC1E,EAAE,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,SAAA,EAAW,SAAS,SAAA,EAAU;AAAA,EAC3D,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,MAAA,EAAQ,SAAS,MAAA,EAAO;AAAA,EACnD,EAAE,IAAA,EAAM,cAAA,EAAgB,IAAA,EAAM,UAAA,EAAY,SAAS,UAAA;AACrD,CAAC;AAMM,SAAS,WAAA,CAAY,MAAc,MAAA,EAAyB;AACjE,EAAA,OAAO,IAAA,KAAS,MAAA,IAAU,IAAA,CAAK,QAAA,CAAS,MAAM,MAAM,CAAA;AACtD;AAEA,SAAS,YAAY,GAAA,EAA+C;AAClE,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,KAAK,WAAA,EAAY;AAAA,EACvC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAOO,SAAS,qBAAqB,IAAA,EAAmD;AACtF,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,EAAA,MAAM,KAAA,GAAQ,KAAK,WAAA,EAAY;AAC/B,EAAA,OAAO,wBAAA,CAAyB,KAAK,CAAC,CAAA,KAAM,YAAY,KAAA,EAAO,CAAA,CAAE,IAAI,CAAC,CAAA,IAAK,IAAA;AAC7E;AAMO,SAAS,oBAAoB,GAAA,EAAkD;AACpF,EAAA,OAAO,oBAAA,CAAqB,WAAA,CAAY,GAAG,CAAC,CAAA;AAC9C;;;ACpCO,IAAM,6BAAA,GAAgC;AAE7C,SAAS,SAAS,KAAA,EAAkD;AAClE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA;AAChD;AAGA,SAAS,kBAAkB,MAAA,EAA0C;AACnE,EAAA,MAAM,SAAS,MAAA,CAAO,iBAAA;AACtB,EAAA,OAAO,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,CAAO,MAAA,GAAS,CAAA;AACvD;AAEA,SAAS,OAAO,GAAA,EAA4B;AAC1C,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,KAAK,WAAA,EAAY;AAAA,EACvC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAMO,SAAS,sBAAsB,MAAA,EAA0B;AAC9D,EAAA,OAAO,QAAA,CAAS,MAAM,CAAA,IAAK,iBAAA,CAAkB,MAAM,CAAA;AACrD;AAsBO,SAAS,uBAAA,CAAwB,MAAA,EAAiB,IAAA,GAA6B,EAAC,EAAY;AACjG,EAAA,IAAI,CAAC,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,KAAA;AAG9B,EAAA,IAAI,iBAAA,CAAkB,MAAM,CAAA,EAAG,OAAO,IAAA;AAGtC,EAAA,IAAI,IAAA,CAAK,qBAAA,KAA0B,IAAA,EAAM,OAAO,IAAA;AAEhD,EAAA,MAAM,QAAQ,MAAA,CAAO,KAAA;AACrB,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,GAAG,OAAO,KAAA;AAElC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,eAAA,EAAiB,WAAA,EAAY;AAClD,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,CAAC,QAAA,CAAS,IAAI,KAAK,OAAO,IAAA,CAAK,QAAQ,QAAA,EAAU;AAGrD,IAAA,IAAI,IAAA,CAAK,GAAA,KAAQ,QAAA,EAAU,OAAO,IAAA;AAGlC,IAAA,IAAI,KAAK,GAAA,KAAQ,SAAA,IAAa,WAAW,OAAO,IAAA,CAAK,QAAQ,QAAA,EAAU;AACrE,MAAA,MAAM,QAAA,GAAW,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA;AAChC,MAAA,IAAI,QAAA,IAAY,QAAA,KAAa,OAAA,EAAS,OAAO,IAAA;AAAA,IAC/C;AAAA,EACF;AAEA,EAAA,OAAO,KAAA;AACT;;;ACrEO,IAAM,8BAAA,GAAiC;AACvC,IAAM,2BAAA,GAA8B;AACpC,IAAM,+BAAA,GAAkC;AACxC,IAAM,4BAAA,GAA+B;AAQrC,IAAM,8BAAA,GAAiC;AAE9C,IAAM,cAAA,uBAAqB,GAAA,CAAY;AAAA,EACrC,8BAAA;AAAA,EACA,2BAAA;AAAA,EACA,+BAAA;AAAA,EACA;AACF,CAAC,CAAA;AAOM,SAAS,oBAAoB,GAAA,EAAsB;AACxD,EAAA,IAAI,CAAC,GAAA,CAAI,UAAA,CAAW,OAAO,GAAG,OAAO,KAAA;AACrC,EAAA,MAAM,WAAW,GAAA,CAAI,KAAA,CAAM,QAAQ,MAAM,CAAA,CAAE,MAAM,GAAG,CAAA;AACpD,EAAA,MAAM,UAAA,GAAa,SAAS,CAAC,CAAA;AAC7B,EAAA,OAAO,UAAA,KAAe,MAAA,IAAa,cAAA,CAAe,GAAA,CAAI,UAAU,CAAA;AAClE;AAOO,SAAS,6BAA6B,IAAA,EAA8C;AACzF,EAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAClB,EAAA,OAAO,KAAK,IAAA,CAAK,CAAC,MAAM,mBAAA,CAAoB,CAAA,CAAE,GAAG,CAAC,CAAA;AACpD","file":"index.cjs","sourcesContent":["import { z } from 'zod';\n\n/**\n * Grapheme-aware refinement matching the AT Protocol lexicon `maxGraphemes`\n * constraint. JS strings are sequences of UTF-16 code units, but lexicon\n * `maxGraphemes` counts user-perceived characters (grapheme clusters), so\n * emoji sequences, regional indicators, ZWJ joins, and combining marks all\n * count as one unit each. We use `Intl.Segmenter` to enforce this correctly.\n */\nexport function maxGraphemes(max: number) {\n return (value: string): boolean => {\n const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n let count = 0;\n for (const _ of segmenter.segment(value)) {\n count++;\n if (count > max) return false;\n }\n return true;\n };\n}\n\n/** Decentralized identifier, AT Protocol `format: did`. */\nexport const didSchema = z.string().regex(/^did:[a-z]+:[a-zA-Z0-9._:%-]+$/, 'Invalid DID');\n\n/** RFC 3339 datetime with timezone offset, AT Protocol `format: datetime`. */\nexport const datetimeSchema = z.string().datetime({ offset: true });\n\n/**\n * Freeform calendar date for user-facing profile dates (start/end, issued,\n * completed, awarded, published, etc.). Accepts a bare year, year-month,\n * year-month-day, or a full datetime (so a legacy record's RFC-3339 date maps\n * through on backfill without loss). NOT strict `datetime` -- users typically\n * remember only month/year, and LinkedIn-importer writes carry partial dates,\n * so the lexicons document these as freeform `YYYY-MM` / `YYYY-MM-DD`. Record\n * metadata like `createdAt` stays on `datetimeSchema`.\n */\nexport const partialDateSchema = z\n .string()\n .regex(/^\\d{4}(-\\d{2}(-\\d{2}(T.+)?)?)?$/, 'Expected YYYY, YYYY-MM, YYYY-MM-DD, or a datetime');\n\n/** Generic AT-URI, AT Protocol `format: at-uri`. */\nexport const atUriSchema = z.string().regex(/^at:\\/\\/[^\\s]+$/, 'Invalid AT-URI');\n\n/** Content identifier, AT Protocol `format: cid`. Loose validation -- accepts CIDv0 and CIDv1. */\nexport const cidSchema = z\n .string()\n .regex(/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|b[A-Za-z0-9+/=]+)$/, 'Invalid CID');\n\n/** BCP 47 language tag, AT Protocol `format: language`. */\nexport const languageTagSchema = z\n .string()\n .regex(/^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/, 'Invalid BCP 47 language tag');\n\n/** Generic URI, AT Protocol `format: uri`. */\nexport const uriSchema = z.string().url();\n\n/**\n * StrongRef shape from `com.atproto.repo.strongRef` -- pins a record by both\n * AT-URI (identity) and CID (version).\n */\nexport const strongRefSchema = z.object({\n uri: atUriSchema,\n cid: cidSchema,\n});\n\n/**\n * Reference to an `id.sifa.profile.skill` record by AT-URI. Mirrors\n * `id.sifa.defs#skillRef`, which carries no CID at all: skills are mutable\n * records in the same user's repo, so the reference resolves live and tracks\n * edits to the target.\n */\nexport const skillRefSchema = z.object({\n uri: atUriSchema,\n});\n\n/**\n * Reference to a record by AT-URI, with an optional CID. Mirrors\n * `id.sifa.defs#externalRecordRef`. Unlike a strongRef the CID is optional:\n * consumers resolve the AT-URI live so the reference tracks edits to the\n * target, and the CID is a best-effort integrity hint only.\n */\nexport const externalRecordRefSchema = z.object({\n uri: atUriSchema,\n cid: cidSchema.optional(),\n});\n\n/**\n * Shared reference to an organization or person named by a record. Mirrors\n * `id.sifa.defs#agentRef`. `name` is a point-in-time snapshot label captured\n * when the record was saved; `did` and `entityRef` are the durable identity and\n * should be resolved live. `name` is required so an empty object cannot validate\n * as a no-op. `.passthrough()` (not `.strict()`): external apps may emit fields a\n * newer schema will define, and dropping them on a round-trip would silently\n * lose data.\n */\nexport const agentRefSchema = z\n .object({\n name: z.string().min(1).refine(maxGraphemes(256)).max(2560),\n did: didSchema.optional(),\n entityRef: uriSchema.optional(),\n })\n .passthrough();\n\nexport type AgentRef = z.infer<typeof agentRefSchema>;\n\n/**\n * Self-labels shape from `com.atproto.label.defs#selfLabels`. Modelled\n * permissively because clients rarely construct this directly; the AppView\n * handles label validation.\n */\nexport const selfLabelsSchema = z.object({\n $type: z.literal('com.atproto.label.defs#selfLabels').optional(),\n values: z.array(z.object({ val: z.string() })),\n});\n","import { z } from 'zod';\n\nimport {\n atUriSchema,\n datetimeSchema,\n didSchema,\n maxGraphemes,\n selfLabelsSchema,\n strongRefSchema,\n uriSchema,\n} from '../schemas/shared.js';\n\n/**\n * Zod schemas mirroring the canonical Standard.site lexicons that Sifa\n * consumes when rendering publication embeds.\n *\n * Canonical lexicons live at:\n * DID: did:plc:re3ebnp5v7ffagz6rb6xfei4\n * PDS: https://auriporia.us-west.host.bsky.network\n * Collection: com.atproto.lexicon.schema\n *\n * Sifa does NOT own these lexicons; we vendor the validation shapes so\n * clients can parse augmented embeds and (in Phase 4) write subscription\n * records to viewers' PDSes. Re-check against the canonical PDS on each\n * SDK release to detect schema drift.\n */\n\nconst rgbColorSchema = z.object({\n r: z.number().int().min(0).max(255),\n g: z.number().int().min(0).max(255),\n b: z.number().int().min(0).max(255),\n});\n\nexport type RgbColor = z.infer<typeof rgbColorSchema>;\n\n/** site.standard.theme.basic */\nexport const BasicThemeSchema = z.object({\n background: rgbColorSchema,\n foreground: rgbColorSchema,\n accent: rgbColorSchema,\n accentForeground: rgbColorSchema,\n});\n\nexport type BasicTheme = z.infer<typeof BasicThemeSchema>;\n\nconst blobRefSchema = z\n .object({\n $type: z.string().optional(),\n ref: z.unknown(),\n mimeType: z.string().optional(),\n size: z.number().optional(),\n })\n .passthrough();\n\n/** site.standard.publication */\nexport const StandardSitePublicationRecordSchema = z\n .object({\n url: uriSchema,\n name: z.string().min(1).refine(maxGraphemes(500)).max(5000),\n description: z.string().refine(maxGraphemes(3000)).max(30000).optional(),\n icon: blobRefSchema.optional(),\n basicTheme: BasicThemeSchema.optional(),\n labels: selfLabelsSchema.optional(),\n preferences: z\n .object({\n showInDiscover: z.boolean().optional(),\n })\n .partial()\n .optional(),\n })\n .passthrough();\n\nexport type StandardSitePublicationRecord = z.infer<typeof StandardSitePublicationRecordSchema>;\n\nconst contributorSchema = z\n .object({\n did: didSchema,\n role: z.string().refine(maxGraphemes(100)).max(1000).optional(),\n displayName: z.string().refine(maxGraphemes(100)).max(1000).optional(),\n })\n .passthrough();\n\n/** site.standard.document */\nexport const StandardSiteDocumentRecordSchema = z\n .object({\n site: z.string().min(1),\n title: z.string().min(1).refine(maxGraphemes(500)).max(5000),\n path: z.string().optional(),\n publishedAt: datetimeSchema,\n updatedAt: datetimeSchema.optional(),\n description: z.string().refine(maxGraphemes(3000)).max(30000).optional(),\n textContent: z.string().optional(),\n tags: z.array(z.string().refine(maxGraphemes(128)).max(1280)).optional(),\n coverImage: blobRefSchema.optional(),\n contributors: z.array(contributorSchema).optional(),\n bskyPostRef: strongRefSchema.optional(),\n labels: selfLabelsSchema.optional(),\n })\n .passthrough();\n\nexport type StandardSiteDocumentRecord = z.infer<typeof StandardSiteDocumentRecordSchema>;\n\n/**\n * site.standard.graph.subscription\n *\n * Authored by a viewer to declare a subscription to a publication. Used\n * by Phase 4 inline-subscribe path. Record key is a TID; the AT-URI\n * shape is `at://<viewer-did>/site.standard.graph.subscription/<rkey>`.\n */\nexport const StandardSiteSubscriptionRecordSchema = z.object({\n publication: atUriSchema,\n createdAt: datetimeSchema.optional(),\n});\n\nexport type StandardSiteSubscriptionRecord = z.infer<typeof StandardSiteSubscriptionRecordSchema>;\n\n/**\n * site.standard.graph.recommend\n *\n * Document-level \"like\" record. Same scope as subscription via the\n * site.standard.authSocial OAuth permission-set, so Phase 4 unlocks\n * inline likes alongside inline subscribes.\n */\nexport const StandardSiteRecommendRecordSchema = z.object({\n document: atUriSchema,\n createdAt: datetimeSchema,\n});\n\nexport type StandardSiteRecommendRecord = z.infer<typeof StandardSiteRecommendRecordSchema>;\n","/**\n * Publisher allowlist for the Standard.site rich embed.\n *\n * Mirrors `bluesky-social/social-app`\n * `src/components/Post/Embed/StandardSiteEmbed/publishers.ts`. The only\n * gate this provides is which publishers get the highlighted\n * \"Subscribe on {Publisher}\" CTA with a publisher icon vs the plain\n * \"View publication\" fallback — any Standard.site embed renders\n * regardless of allowlist membership.\n *\n * Sync policy: review additions against the upstream bsky list weekly;\n * Singi Labs reviews before merge.\n */\n\nexport interface Publisher {\n /** Lowercase host. Subdomains are matched via `hostMatches`. */\n host: string;\n /** Human-facing publisher name shown in CTA copy. */\n name: string;\n /**\n * Stable identifier used by clients to look up brand icons. The SDK\n * does not ship icon assets — sifa-web maintains them keyed by this\n * id so React Native vs web rendering can diverge.\n */\n iconKey: 'leaflet' | 'pckt' | 'offprint';\n}\n\nexport const STANDARD_SITE_PUBLISHERS: readonly Publisher[] = Object.freeze([\n { host: 'leaflet.pub', name: 'Leaflet', iconKey: 'leaflet' },\n { host: 'pckt.blog', name: 'pckt', iconKey: 'pckt' },\n { host: 'offprint.app', name: 'Offprint', iconKey: 'offprint' },\n]);\n\n/**\n * Returns true when `host` exactly matches `target` or is a subdomain of\n * it. Hosts are expected to be lowercase already.\n */\nexport function hostMatches(host: string, target: string): boolean {\n return host === target || host.endsWith('.' + target);\n}\n\nfunction hostFromUri(uri: string | undefined | null): string | null {\n if (!uri) return null;\n try {\n return new URL(uri).host.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * Match a publisher by host. Returns the publisher when `host` is on the\n * allowlist (exact or subdomain match), null otherwise. Host should be\n * lowercase.\n */\nexport function matchPublisherByHost(host: string | null | undefined): Publisher | null {\n if (!host) return null;\n const lower = host.toLowerCase();\n return STANDARD_SITE_PUBLISHERS.find((p) => hostMatches(lower, p.host)) ?? null;\n}\n\n/**\n * Match a publisher by URI. Convenience wrapper around\n * `matchPublisherByHost` for callers that have a publication URL handy.\n */\nexport function matchPublisherByUri(uri: string | undefined | null): Publisher | null {\n return matchPublisherByHost(hostFromUri(uri));\n}\n","/**\n * Linkblog-share detection for the Standard.site document family.\n *\n * A \"linkblog\" (e.g. Skyreader) writes a `site.standard.document` to the\n * user's PDS for every *external* article they share. Those documents are\n * valid Standard.site posts, so they surface in Sifa's activity stream and\n * profile portfolio as if the user authored them — but the user only shared\n * someone else's writing (sifa-workspace#351).\n *\n * These predicates identify such shares so callers can exclude them, while\n * leaving genuinely authored Standard.site posts (Leaflet, pckt, Offprint\n * essays) untouched.\n *\n * Signals, most to least reliable:\n * 1. The Skyreader provenance marker on the document (`skyreaderLinkblog`).\n * 2. The document's parent publication is itself a linkblog (the marker\n * lives on the publication even when older documents lack their own).\n * Callers resolve the publication and pass `publicationIsLinkblog`.\n * 3. A `links` entry with `rel: \"repost\"` — a quote-reshare, inherently\n * not the user's own writing.\n * 4. Heuristic: a `links` entry with `rel: \"related\"` pointing to an\n * external host different from the document's own publication host —\n * the document's subject is an off-site article. Only applied when the\n * caller supplies the resolved `publicationHost`, so an authored post\n * that merely cites an external resource is never hidden on a guess.\n */\n\n/**\n * Discovery marker Skyreader stamps on every linkblog publication and on\n * link-share documents. Value is a fixed URL, not a per-user value.\n */\nexport const SKYREADER_LINKBLOG_MARKER_URL = 'https://skyreader.app/linkblog';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** A non-empty `skyreaderLinkblog` marker field. */\nfunction hasLinkblogMarker(record: Record<string, unknown>): boolean {\n const marker = record.skyreaderLinkblog;\n return typeof marker === 'string' && marker.length > 0;\n}\n\nfunction hostOf(uri: string): string | null {\n try {\n return new URL(uri).host.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * True when a `site.standard.publication` record is a linkblog rather than an\n * authored publication — i.e. it carries the Skyreader provenance marker.\n */\nexport function isLinkblogPublication(record: unknown): boolean {\n return isRecord(record) && hasLinkblogMarker(record);\n}\n\nexport interface LinkblogShareOptions {\n /**\n * True when the document's parent publication has been resolved and is\n * itself a linkblog (see {@link isLinkblogPublication}). Catches shares\n * whose document predates the per-document marker.\n */\n publicationIsLinkblog?: boolean;\n /**\n * Resolved host of the document's parent publication, enabling the\n * external-subject heuristic. Omit when the publication (hence its host)\n * could not be resolved — the heuristic then stays off.\n */\n publicationHost?: string;\n}\n\n/**\n * True when a `site.standard.document` is a shared/linkblogged external\n * article rather than the user's own writing. See the module comment for the\n * signal order.\n */\nexport function isLinkblogShareDocument(record: unknown, opts: LinkblogShareOptions = {}): boolean {\n if (!isRecord(record)) return false;\n\n // 1. Provenance marker on the document itself.\n if (hasLinkblogMarker(record)) return true;\n\n // 2. Parent publication is a linkblog (caller-resolved).\n if (opts.publicationIsLinkblog === true) return true;\n\n const links = record.links;\n if (!Array.isArray(links)) return false;\n\n const pubHost = opts.publicationHost?.toLowerCase();\n for (const link of links) {\n if (!isRecord(link) || typeof link.rel !== 'string') continue;\n\n // 3. Reshare of another record.\n if (link.rel === 'repost') return true;\n\n // 4. External-subject heuristic (only with a known publication host).\n if (link.rel === 'related' && pubHost && typeof link.uri === 'string') {\n const linkHost = hostOf(link.uri);\n if (linkHost && linkHost !== pubHost) return true;\n }\n }\n\n return false;\n}\n","import type { BasicTheme } from './schemas.js';\n\n/**\n * Publication metadata embedded in the augmented activity view by\n * sifa-api when an external link card matches an indexed publication.\n *\n * `uri` is the publication's AT-URI (e.g.\n * `at://did:plc:abc/site.standard.publication/xyz`). `theme` carries\n * the publication's own colors when supplied — clients should apply a\n * WCAG min-contrast fallback before honoring them.\n *\n * `icon` is left as `unknown` here because its representation depends\n * on the surface: blob ref on the wire, CDN URL after resolution.\n */\nexport interface PublicationSource {\n uri: string;\n title: string;\n icon?: unknown;\n theme?: BasicTheme;\n}\n\n/**\n * Shape attached to activity items whose external link card URL matches\n * a Standard.site record. Mirrors the relevant fields of bsky's\n * `StandardSiteEmbed` view so a sifa-web renderer can be near-1:1.\n *\n * `associatedRefs` carries AT-URIs the client may use in Phase 4 to\n * query subscription state for the viewer.\n */\nexport interface StandardSiteEmbedView {\n uri: string;\n source: PublicationSource;\n associatedRefs: { uri: string }[];\n createdAt?: string;\n readingTime?: number;\n}\n\nexport const STANDARD_SITE_PUBLICATION_NSID = 'site.standard.publication' as const;\nexport const STANDARD_SITE_DOCUMENT_NSID = 'site.standard.document' as const;\nexport const STANDARD_SITE_SUBSCRIPTION_NSID = 'site.standard.graph.subscription' as const;\nexport const STANDARD_SITE_RECOMMEND_NSID = 'site.standard.graph.recommend' as const;\n\n/**\n * Pre-defined OAuth permission-set the consumer apps request to write\n * subscription + recommend records on behalf of the viewer. The actual\n * permission-set is published under\n * `at://did:plc:re3ebnp5v7ffagz6rb6xfei4/com.atproto.lexicon.schema/site.standard.authSocial`.\n */\nexport const STANDARD_SITE_AUTH_SOCIAL_NSID = 'site.standard.authSocial' as const;\n\nconst COLLECTION_SET = new Set<string>([\n STANDARD_SITE_PUBLICATION_NSID,\n STANDARD_SITE_DOCUMENT_NSID,\n STANDARD_SITE_SUBSCRIPTION_NSID,\n STANDARD_SITE_RECOMMEND_NSID,\n]);\n\n/**\n * True when the AT-URI's collection segment is a Standard.site\n * collection. Useful for detecting Standard.site embed augmentation on\n * arbitrary activity items.\n */\nexport function isStandardSiteAtUri(uri: string): boolean {\n if (!uri.startsWith('at://')) return false;\n const segments = uri.slice('at://'.length).split('/');\n const collection = segments[1];\n return collection !== undefined && COLLECTION_SET.has(collection);\n}\n\n/**\n * True when any `associatedRefs[].uri` is a Standard.site collection\n * AT-URI. The bsky client uses the same check to gate the\n * `StandardSiteEmbed` renderer.\n */\nexport function hasStandardSiteAssociatedRef(refs: { uri: string }[] | undefined): boolean {\n if (!refs) return false;\n return refs.some((r) => isStandardSiteAtUri(r.uri));\n}\n"]}
@@ -30,6 +30,11 @@ z.object({
30
30
  uri: atUriSchema,
31
31
  cid: cidSchema.optional()
32
32
  });
33
+ z.object({
34
+ name: z.string().min(1).refine(maxGraphemes(256)).max(2560),
35
+ did: didSchema.optional(),
36
+ entityRef: uriSchema.optional()
37
+ }).passthrough();
33
38
  var selfLabelsSchema = z.object({
34
39
  $type: z.literal("com.atproto.label.defs#selfLabels").optional(),
35
40
  values: z.array(z.object({ val: z.string() }))
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/schemas/shared.ts","../../src/publishing/schemas.ts","../../src/publishing/registry.ts","../../src/publishing/linkblog.ts","../../src/publishing/types.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["import { z } from 'zod';\n\n/**\n * Grapheme-aware refinement matching the AT Protocol lexicon `maxGraphemes`\n * constraint. JS strings are sequences of UTF-16 code units, but lexicon\n * `maxGraphemes` counts user-perceived characters (grapheme clusters), so\n * emoji sequences, regional indicators, ZWJ joins, and combining marks all\n * count as one unit each. We use `Intl.Segmenter` to enforce this correctly.\n */\nexport function maxGraphemes(max: number) {\n return (value: string): boolean => {\n const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n let count = 0;\n for (const _ of segmenter.segment(value)) {\n count++;\n if (count > max) return false;\n }\n return true;\n };\n}\n\n/** Decentralized identifier, AT Protocol `format: did`. */\nexport const didSchema = z.string().regex(/^did:[a-z]+:[a-zA-Z0-9._:%-]+$/, 'Invalid DID');\n\n/** RFC 3339 datetime with timezone offset, AT Protocol `format: datetime`. */\nexport const datetimeSchema = z.string().datetime({ offset: true });\n\n/**\n * Freeform calendar date for user-facing profile dates (start/end, issued,\n * completed, awarded, published, etc.). Accepts a bare year, year-month,\n * year-month-day, or a full datetime (so a legacy record's RFC-3339 date maps\n * through on backfill without loss). NOT strict `datetime` -- users typically\n * remember only month/year, and LinkedIn-importer writes carry partial dates,\n * so the lexicons document these as freeform `YYYY-MM` / `YYYY-MM-DD`. Record\n * metadata like `createdAt` stays on `datetimeSchema`.\n */\nexport const partialDateSchema = z\n .string()\n .regex(/^\\d{4}(-\\d{2}(-\\d{2}(T.+)?)?)?$/, 'Expected YYYY, YYYY-MM, YYYY-MM-DD, or a datetime');\n\n/** Generic AT-URI, AT Protocol `format: at-uri`. */\nexport const atUriSchema = z.string().regex(/^at:\\/\\/[^\\s]+$/, 'Invalid AT-URI');\n\n/** Content identifier, AT Protocol `format: cid`. Loose validation -- accepts CIDv0 and CIDv1. */\nexport const cidSchema = z\n .string()\n .regex(/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|b[A-Za-z0-9+/=]+)$/, 'Invalid CID');\n\n/** BCP 47 language tag, AT Protocol `format: language`. */\nexport const languageTagSchema = z\n .string()\n .regex(/^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/, 'Invalid BCP 47 language tag');\n\n/** Generic URI, AT Protocol `format: uri`. */\nexport const uriSchema = z.string().url();\n\n/**\n * StrongRef shape from `com.atproto.repo.strongRef` -- pins a record by both\n * AT-URI (identity) and CID (version).\n */\nexport const strongRefSchema = z.object({\n uri: atUriSchema,\n cid: cidSchema,\n});\n\n/**\n * Reference to an `id.sifa.profile.skill` record by AT-URI. Mirrors\n * `id.sifa.defs#skillRef`, which carries no CID at all: skills are mutable\n * records in the same user's repo, so the reference resolves live and tracks\n * edits to the target.\n */\nexport const skillRefSchema = z.object({\n uri: atUriSchema,\n});\n\n/**\n * Reference to a record by AT-URI, with an optional CID. Mirrors\n * `id.sifa.defs#externalRecordRef`. Unlike a strongRef the CID is optional:\n * consumers resolve the AT-URI live so the reference tracks edits to the\n * target, and the CID is a best-effort integrity hint only.\n */\nexport const externalRecordRefSchema = z.object({\n uri: atUriSchema,\n cid: cidSchema.optional(),\n});\n\n/**\n * Self-labels shape from `com.atproto.label.defs#selfLabels`. Modelled\n * permissively because clients rarely construct this directly; the AppView\n * handles label validation.\n */\nexport const selfLabelsSchema = z.object({\n $type: z.literal('com.atproto.label.defs#selfLabels').optional(),\n values: z.array(z.object({ val: z.string() })),\n});\n","import { z } from 'zod';\n\nimport {\n atUriSchema,\n datetimeSchema,\n didSchema,\n maxGraphemes,\n selfLabelsSchema,\n strongRefSchema,\n uriSchema,\n} from '../schemas/shared.js';\n\n/**\n * Zod schemas mirroring the canonical Standard.site lexicons that Sifa\n * consumes when rendering publication embeds.\n *\n * Canonical lexicons live at:\n * DID: did:plc:re3ebnp5v7ffagz6rb6xfei4\n * PDS: https://auriporia.us-west.host.bsky.network\n * Collection: com.atproto.lexicon.schema\n *\n * Sifa does NOT own these lexicons; we vendor the validation shapes so\n * clients can parse augmented embeds and (in Phase 4) write subscription\n * records to viewers' PDSes. Re-check against the canonical PDS on each\n * SDK release to detect schema drift.\n */\n\nconst rgbColorSchema = z.object({\n r: z.number().int().min(0).max(255),\n g: z.number().int().min(0).max(255),\n b: z.number().int().min(0).max(255),\n});\n\nexport type RgbColor = z.infer<typeof rgbColorSchema>;\n\n/** site.standard.theme.basic */\nexport const BasicThemeSchema = z.object({\n background: rgbColorSchema,\n foreground: rgbColorSchema,\n accent: rgbColorSchema,\n accentForeground: rgbColorSchema,\n});\n\nexport type BasicTheme = z.infer<typeof BasicThemeSchema>;\n\nconst blobRefSchema = z\n .object({\n $type: z.string().optional(),\n ref: z.unknown(),\n mimeType: z.string().optional(),\n size: z.number().optional(),\n })\n .passthrough();\n\n/** site.standard.publication */\nexport const StandardSitePublicationRecordSchema = z\n .object({\n url: uriSchema,\n name: z.string().min(1).refine(maxGraphemes(500)).max(5000),\n description: z.string().refine(maxGraphemes(3000)).max(30000).optional(),\n icon: blobRefSchema.optional(),\n basicTheme: BasicThemeSchema.optional(),\n labels: selfLabelsSchema.optional(),\n preferences: z\n .object({\n showInDiscover: z.boolean().optional(),\n })\n .partial()\n .optional(),\n })\n .passthrough();\n\nexport type StandardSitePublicationRecord = z.infer<typeof StandardSitePublicationRecordSchema>;\n\nconst contributorSchema = z\n .object({\n did: didSchema,\n role: z.string().refine(maxGraphemes(100)).max(1000).optional(),\n displayName: z.string().refine(maxGraphemes(100)).max(1000).optional(),\n })\n .passthrough();\n\n/** site.standard.document */\nexport const StandardSiteDocumentRecordSchema = z\n .object({\n site: z.string().min(1),\n title: z.string().min(1).refine(maxGraphemes(500)).max(5000),\n path: z.string().optional(),\n publishedAt: datetimeSchema,\n updatedAt: datetimeSchema.optional(),\n description: z.string().refine(maxGraphemes(3000)).max(30000).optional(),\n textContent: z.string().optional(),\n tags: z.array(z.string().refine(maxGraphemes(128)).max(1280)).optional(),\n coverImage: blobRefSchema.optional(),\n contributors: z.array(contributorSchema).optional(),\n bskyPostRef: strongRefSchema.optional(),\n labels: selfLabelsSchema.optional(),\n })\n .passthrough();\n\nexport type StandardSiteDocumentRecord = z.infer<typeof StandardSiteDocumentRecordSchema>;\n\n/**\n * site.standard.graph.subscription\n *\n * Authored by a viewer to declare a subscription to a publication. Used\n * by Phase 4 inline-subscribe path. Record key is a TID; the AT-URI\n * shape is `at://<viewer-did>/site.standard.graph.subscription/<rkey>`.\n */\nexport const StandardSiteSubscriptionRecordSchema = z.object({\n publication: atUriSchema,\n createdAt: datetimeSchema.optional(),\n});\n\nexport type StandardSiteSubscriptionRecord = z.infer<typeof StandardSiteSubscriptionRecordSchema>;\n\n/**\n * site.standard.graph.recommend\n *\n * Document-level \"like\" record. Same scope as subscription via the\n * site.standard.authSocial OAuth permission-set, so Phase 4 unlocks\n * inline likes alongside inline subscribes.\n */\nexport const StandardSiteRecommendRecordSchema = z.object({\n document: atUriSchema,\n createdAt: datetimeSchema,\n});\n\nexport type StandardSiteRecommendRecord = z.infer<typeof StandardSiteRecommendRecordSchema>;\n","/**\n * Publisher allowlist for the Standard.site rich embed.\n *\n * Mirrors `bluesky-social/social-app`\n * `src/components/Post/Embed/StandardSiteEmbed/publishers.ts`. The only\n * gate this provides is which publishers get the highlighted\n * \"Subscribe on {Publisher}\" CTA with a publisher icon vs the plain\n * \"View publication\" fallback — any Standard.site embed renders\n * regardless of allowlist membership.\n *\n * Sync policy: review additions against the upstream bsky list weekly;\n * Singi Labs reviews before merge.\n */\n\nexport interface Publisher {\n /** Lowercase host. Subdomains are matched via `hostMatches`. */\n host: string;\n /** Human-facing publisher name shown in CTA copy. */\n name: string;\n /**\n * Stable identifier used by clients to look up brand icons. The SDK\n * does not ship icon assets — sifa-web maintains them keyed by this\n * id so React Native vs web rendering can diverge.\n */\n iconKey: 'leaflet' | 'pckt' | 'offprint';\n}\n\nexport const STANDARD_SITE_PUBLISHERS: readonly Publisher[] = Object.freeze([\n { host: 'leaflet.pub', name: 'Leaflet', iconKey: 'leaflet' },\n { host: 'pckt.blog', name: 'pckt', iconKey: 'pckt' },\n { host: 'offprint.app', name: 'Offprint', iconKey: 'offprint' },\n]);\n\n/**\n * Returns true when `host` exactly matches `target` or is a subdomain of\n * it. Hosts are expected to be lowercase already.\n */\nexport function hostMatches(host: string, target: string): boolean {\n return host === target || host.endsWith('.' + target);\n}\n\nfunction hostFromUri(uri: string | undefined | null): string | null {\n if (!uri) return null;\n try {\n return new URL(uri).host.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * Match a publisher by host. Returns the publisher when `host` is on the\n * allowlist (exact or subdomain match), null otherwise. Host should be\n * lowercase.\n */\nexport function matchPublisherByHost(host: string | null | undefined): Publisher | null {\n if (!host) return null;\n const lower = host.toLowerCase();\n return STANDARD_SITE_PUBLISHERS.find((p) => hostMatches(lower, p.host)) ?? null;\n}\n\n/**\n * Match a publisher by URI. Convenience wrapper around\n * `matchPublisherByHost` for callers that have a publication URL handy.\n */\nexport function matchPublisherByUri(uri: string | undefined | null): Publisher | null {\n return matchPublisherByHost(hostFromUri(uri));\n}\n","/**\n * Linkblog-share detection for the Standard.site document family.\n *\n * A \"linkblog\" (e.g. Skyreader) writes a `site.standard.document` to the\n * user's PDS for every *external* article they share. Those documents are\n * valid Standard.site posts, so they surface in Sifa's activity stream and\n * profile portfolio as if the user authored them — but the user only shared\n * someone else's writing (sifa-workspace#351).\n *\n * These predicates identify such shares so callers can exclude them, while\n * leaving genuinely authored Standard.site posts (Leaflet, pckt, Offprint\n * essays) untouched.\n *\n * Signals, most to least reliable:\n * 1. The Skyreader provenance marker on the document (`skyreaderLinkblog`).\n * 2. The document's parent publication is itself a linkblog (the marker\n * lives on the publication even when older documents lack their own).\n * Callers resolve the publication and pass `publicationIsLinkblog`.\n * 3. A `links` entry with `rel: \"repost\"` — a quote-reshare, inherently\n * not the user's own writing.\n * 4. Heuristic: a `links` entry with `rel: \"related\"` pointing to an\n * external host different from the document's own publication host —\n * the document's subject is an off-site article. Only applied when the\n * caller supplies the resolved `publicationHost`, so an authored post\n * that merely cites an external resource is never hidden on a guess.\n */\n\n/**\n * Discovery marker Skyreader stamps on every linkblog publication and on\n * link-share documents. Value is a fixed URL, not a per-user value.\n */\nexport const SKYREADER_LINKBLOG_MARKER_URL = 'https://skyreader.app/linkblog';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** A non-empty `skyreaderLinkblog` marker field. */\nfunction hasLinkblogMarker(record: Record<string, unknown>): boolean {\n const marker = record.skyreaderLinkblog;\n return typeof marker === 'string' && marker.length > 0;\n}\n\nfunction hostOf(uri: string): string | null {\n try {\n return new URL(uri).host.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * True when a `site.standard.publication` record is a linkblog rather than an\n * authored publication — i.e. it carries the Skyreader provenance marker.\n */\nexport function isLinkblogPublication(record: unknown): boolean {\n return isRecord(record) && hasLinkblogMarker(record);\n}\n\nexport interface LinkblogShareOptions {\n /**\n * True when the document's parent publication has been resolved and is\n * itself a linkblog (see {@link isLinkblogPublication}). Catches shares\n * whose document predates the per-document marker.\n */\n publicationIsLinkblog?: boolean;\n /**\n * Resolved host of the document's parent publication, enabling the\n * external-subject heuristic. Omit when the publication (hence its host)\n * could not be resolved — the heuristic then stays off.\n */\n publicationHost?: string;\n}\n\n/**\n * True when a `site.standard.document` is a shared/linkblogged external\n * article rather than the user's own writing. See the module comment for the\n * signal order.\n */\nexport function isLinkblogShareDocument(record: unknown, opts: LinkblogShareOptions = {}): boolean {\n if (!isRecord(record)) return false;\n\n // 1. Provenance marker on the document itself.\n if (hasLinkblogMarker(record)) return true;\n\n // 2. Parent publication is a linkblog (caller-resolved).\n if (opts.publicationIsLinkblog === true) return true;\n\n const links = record.links;\n if (!Array.isArray(links)) return false;\n\n const pubHost = opts.publicationHost?.toLowerCase();\n for (const link of links) {\n if (!isRecord(link) || typeof link.rel !== 'string') continue;\n\n // 3. Reshare of another record.\n if (link.rel === 'repost') return true;\n\n // 4. External-subject heuristic (only with a known publication host).\n if (link.rel === 'related' && pubHost && typeof link.uri === 'string') {\n const linkHost = hostOf(link.uri);\n if (linkHost && linkHost !== pubHost) return true;\n }\n }\n\n return false;\n}\n","import type { BasicTheme } from './schemas.js';\n\n/**\n * Publication metadata embedded in the augmented activity view by\n * sifa-api when an external link card matches an indexed publication.\n *\n * `uri` is the publication's AT-URI (e.g.\n * `at://did:plc:abc/site.standard.publication/xyz`). `theme` carries\n * the publication's own colors when supplied — clients should apply a\n * WCAG min-contrast fallback before honoring them.\n *\n * `icon` is left as `unknown` here because its representation depends\n * on the surface: blob ref on the wire, CDN URL after resolution.\n */\nexport interface PublicationSource {\n uri: string;\n title: string;\n icon?: unknown;\n theme?: BasicTheme;\n}\n\n/**\n * Shape attached to activity items whose external link card URL matches\n * a Standard.site record. Mirrors the relevant fields of bsky's\n * `StandardSiteEmbed` view so a sifa-web renderer can be near-1:1.\n *\n * `associatedRefs` carries AT-URIs the client may use in Phase 4 to\n * query subscription state for the viewer.\n */\nexport interface StandardSiteEmbedView {\n uri: string;\n source: PublicationSource;\n associatedRefs: { uri: string }[];\n createdAt?: string;\n readingTime?: number;\n}\n\nexport const STANDARD_SITE_PUBLICATION_NSID = 'site.standard.publication' as const;\nexport const STANDARD_SITE_DOCUMENT_NSID = 'site.standard.document' as const;\nexport const STANDARD_SITE_SUBSCRIPTION_NSID = 'site.standard.graph.subscription' as const;\nexport const STANDARD_SITE_RECOMMEND_NSID = 'site.standard.graph.recommend' as const;\n\n/**\n * Pre-defined OAuth permission-set the consumer apps request to write\n * subscription + recommend records on behalf of the viewer. The actual\n * permission-set is published under\n * `at://did:plc:re3ebnp5v7ffagz6rb6xfei4/com.atproto.lexicon.schema/site.standard.authSocial`.\n */\nexport const STANDARD_SITE_AUTH_SOCIAL_NSID = 'site.standard.authSocial' as const;\n\nconst COLLECTION_SET = new Set<string>([\n STANDARD_SITE_PUBLICATION_NSID,\n STANDARD_SITE_DOCUMENT_NSID,\n STANDARD_SITE_SUBSCRIPTION_NSID,\n STANDARD_SITE_RECOMMEND_NSID,\n]);\n\n/**\n * True when the AT-URI's collection segment is a Standard.site\n * collection. Useful for detecting Standard.site embed augmentation on\n * arbitrary activity items.\n */\nexport function isStandardSiteAtUri(uri: string): boolean {\n if (!uri.startsWith('at://')) return false;\n const segments = uri.slice('at://'.length).split('/');\n const collection = segments[1];\n return collection !== undefined && COLLECTION_SET.has(collection);\n}\n\n/**\n * True when any `associatedRefs[].uri` is a Standard.site collection\n * AT-URI. The bsky client uses the same check to gate the\n * `StandardSiteEmbed` renderer.\n */\nexport function hasStandardSiteAssociatedRef(refs: { uri: string }[] | undefined): boolean {\n if (!refs) return false;\n return refs.some((r) => isStandardSiteAtUri(r.uri));\n}\n"]}
1
+ {"version":3,"sources":["../../src/schemas/shared.ts","../../src/publishing/schemas.ts","../../src/publishing/registry.ts","../../src/publishing/linkblog.ts","../../src/publishing/types.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["import { z } from 'zod';\n\n/**\n * Grapheme-aware refinement matching the AT Protocol lexicon `maxGraphemes`\n * constraint. JS strings are sequences of UTF-16 code units, but lexicon\n * `maxGraphemes` counts user-perceived characters (grapheme clusters), so\n * emoji sequences, regional indicators, ZWJ joins, and combining marks all\n * count as one unit each. We use `Intl.Segmenter` to enforce this correctly.\n */\nexport function maxGraphemes(max: number) {\n return (value: string): boolean => {\n const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n let count = 0;\n for (const _ of segmenter.segment(value)) {\n count++;\n if (count > max) return false;\n }\n return true;\n };\n}\n\n/** Decentralized identifier, AT Protocol `format: did`. */\nexport const didSchema = z.string().regex(/^did:[a-z]+:[a-zA-Z0-9._:%-]+$/, 'Invalid DID');\n\n/** RFC 3339 datetime with timezone offset, AT Protocol `format: datetime`. */\nexport const datetimeSchema = z.string().datetime({ offset: true });\n\n/**\n * Freeform calendar date for user-facing profile dates (start/end, issued,\n * completed, awarded, published, etc.). Accepts a bare year, year-month,\n * year-month-day, or a full datetime (so a legacy record's RFC-3339 date maps\n * through on backfill without loss). NOT strict `datetime` -- users typically\n * remember only month/year, and LinkedIn-importer writes carry partial dates,\n * so the lexicons document these as freeform `YYYY-MM` / `YYYY-MM-DD`. Record\n * metadata like `createdAt` stays on `datetimeSchema`.\n */\nexport const partialDateSchema = z\n .string()\n .regex(/^\\d{4}(-\\d{2}(-\\d{2}(T.+)?)?)?$/, 'Expected YYYY, YYYY-MM, YYYY-MM-DD, or a datetime');\n\n/** Generic AT-URI, AT Protocol `format: at-uri`. */\nexport const atUriSchema = z.string().regex(/^at:\\/\\/[^\\s]+$/, 'Invalid AT-URI');\n\n/** Content identifier, AT Protocol `format: cid`. Loose validation -- accepts CIDv0 and CIDv1. */\nexport const cidSchema = z\n .string()\n .regex(/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|b[A-Za-z0-9+/=]+)$/, 'Invalid CID');\n\n/** BCP 47 language tag, AT Protocol `format: language`. */\nexport const languageTagSchema = z\n .string()\n .regex(/^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/, 'Invalid BCP 47 language tag');\n\n/** Generic URI, AT Protocol `format: uri`. */\nexport const uriSchema = z.string().url();\n\n/**\n * StrongRef shape from `com.atproto.repo.strongRef` -- pins a record by both\n * AT-URI (identity) and CID (version).\n */\nexport const strongRefSchema = z.object({\n uri: atUriSchema,\n cid: cidSchema,\n});\n\n/**\n * Reference to an `id.sifa.profile.skill` record by AT-URI. Mirrors\n * `id.sifa.defs#skillRef`, which carries no CID at all: skills are mutable\n * records in the same user's repo, so the reference resolves live and tracks\n * edits to the target.\n */\nexport const skillRefSchema = z.object({\n uri: atUriSchema,\n});\n\n/**\n * Reference to a record by AT-URI, with an optional CID. Mirrors\n * `id.sifa.defs#externalRecordRef`. Unlike a strongRef the CID is optional:\n * consumers resolve the AT-URI live so the reference tracks edits to the\n * target, and the CID is a best-effort integrity hint only.\n */\nexport const externalRecordRefSchema = z.object({\n uri: atUriSchema,\n cid: cidSchema.optional(),\n});\n\n/**\n * Shared reference to an organization or person named by a record. Mirrors\n * `id.sifa.defs#agentRef`. `name` is a point-in-time snapshot label captured\n * when the record was saved; `did` and `entityRef` are the durable identity and\n * should be resolved live. `name` is required so an empty object cannot validate\n * as a no-op. `.passthrough()` (not `.strict()`): external apps may emit fields a\n * newer schema will define, and dropping them on a round-trip would silently\n * lose data.\n */\nexport const agentRefSchema = z\n .object({\n name: z.string().min(1).refine(maxGraphemes(256)).max(2560),\n did: didSchema.optional(),\n entityRef: uriSchema.optional(),\n })\n .passthrough();\n\nexport type AgentRef = z.infer<typeof agentRefSchema>;\n\n/**\n * Self-labels shape from `com.atproto.label.defs#selfLabels`. Modelled\n * permissively because clients rarely construct this directly; the AppView\n * handles label validation.\n */\nexport const selfLabelsSchema = z.object({\n $type: z.literal('com.atproto.label.defs#selfLabels').optional(),\n values: z.array(z.object({ val: z.string() })),\n});\n","import { z } from 'zod';\n\nimport {\n atUriSchema,\n datetimeSchema,\n didSchema,\n maxGraphemes,\n selfLabelsSchema,\n strongRefSchema,\n uriSchema,\n} from '../schemas/shared.js';\n\n/**\n * Zod schemas mirroring the canonical Standard.site lexicons that Sifa\n * consumes when rendering publication embeds.\n *\n * Canonical lexicons live at:\n * DID: did:plc:re3ebnp5v7ffagz6rb6xfei4\n * PDS: https://auriporia.us-west.host.bsky.network\n * Collection: com.atproto.lexicon.schema\n *\n * Sifa does NOT own these lexicons; we vendor the validation shapes so\n * clients can parse augmented embeds and (in Phase 4) write subscription\n * records to viewers' PDSes. Re-check against the canonical PDS on each\n * SDK release to detect schema drift.\n */\n\nconst rgbColorSchema = z.object({\n r: z.number().int().min(0).max(255),\n g: z.number().int().min(0).max(255),\n b: z.number().int().min(0).max(255),\n});\n\nexport type RgbColor = z.infer<typeof rgbColorSchema>;\n\n/** site.standard.theme.basic */\nexport const BasicThemeSchema = z.object({\n background: rgbColorSchema,\n foreground: rgbColorSchema,\n accent: rgbColorSchema,\n accentForeground: rgbColorSchema,\n});\n\nexport type BasicTheme = z.infer<typeof BasicThemeSchema>;\n\nconst blobRefSchema = z\n .object({\n $type: z.string().optional(),\n ref: z.unknown(),\n mimeType: z.string().optional(),\n size: z.number().optional(),\n })\n .passthrough();\n\n/** site.standard.publication */\nexport const StandardSitePublicationRecordSchema = z\n .object({\n url: uriSchema,\n name: z.string().min(1).refine(maxGraphemes(500)).max(5000),\n description: z.string().refine(maxGraphemes(3000)).max(30000).optional(),\n icon: blobRefSchema.optional(),\n basicTheme: BasicThemeSchema.optional(),\n labels: selfLabelsSchema.optional(),\n preferences: z\n .object({\n showInDiscover: z.boolean().optional(),\n })\n .partial()\n .optional(),\n })\n .passthrough();\n\nexport type StandardSitePublicationRecord = z.infer<typeof StandardSitePublicationRecordSchema>;\n\nconst contributorSchema = z\n .object({\n did: didSchema,\n role: z.string().refine(maxGraphemes(100)).max(1000).optional(),\n displayName: z.string().refine(maxGraphemes(100)).max(1000).optional(),\n })\n .passthrough();\n\n/** site.standard.document */\nexport const StandardSiteDocumentRecordSchema = z\n .object({\n site: z.string().min(1),\n title: z.string().min(1).refine(maxGraphemes(500)).max(5000),\n path: z.string().optional(),\n publishedAt: datetimeSchema,\n updatedAt: datetimeSchema.optional(),\n description: z.string().refine(maxGraphemes(3000)).max(30000).optional(),\n textContent: z.string().optional(),\n tags: z.array(z.string().refine(maxGraphemes(128)).max(1280)).optional(),\n coverImage: blobRefSchema.optional(),\n contributors: z.array(contributorSchema).optional(),\n bskyPostRef: strongRefSchema.optional(),\n labels: selfLabelsSchema.optional(),\n })\n .passthrough();\n\nexport type StandardSiteDocumentRecord = z.infer<typeof StandardSiteDocumentRecordSchema>;\n\n/**\n * site.standard.graph.subscription\n *\n * Authored by a viewer to declare a subscription to a publication. Used\n * by Phase 4 inline-subscribe path. Record key is a TID; the AT-URI\n * shape is `at://<viewer-did>/site.standard.graph.subscription/<rkey>`.\n */\nexport const StandardSiteSubscriptionRecordSchema = z.object({\n publication: atUriSchema,\n createdAt: datetimeSchema.optional(),\n});\n\nexport type StandardSiteSubscriptionRecord = z.infer<typeof StandardSiteSubscriptionRecordSchema>;\n\n/**\n * site.standard.graph.recommend\n *\n * Document-level \"like\" record. Same scope as subscription via the\n * site.standard.authSocial OAuth permission-set, so Phase 4 unlocks\n * inline likes alongside inline subscribes.\n */\nexport const StandardSiteRecommendRecordSchema = z.object({\n document: atUriSchema,\n createdAt: datetimeSchema,\n});\n\nexport type StandardSiteRecommendRecord = z.infer<typeof StandardSiteRecommendRecordSchema>;\n","/**\n * Publisher allowlist for the Standard.site rich embed.\n *\n * Mirrors `bluesky-social/social-app`\n * `src/components/Post/Embed/StandardSiteEmbed/publishers.ts`. The only\n * gate this provides is which publishers get the highlighted\n * \"Subscribe on {Publisher}\" CTA with a publisher icon vs the plain\n * \"View publication\" fallback — any Standard.site embed renders\n * regardless of allowlist membership.\n *\n * Sync policy: review additions against the upstream bsky list weekly;\n * Singi Labs reviews before merge.\n */\n\nexport interface Publisher {\n /** Lowercase host. Subdomains are matched via `hostMatches`. */\n host: string;\n /** Human-facing publisher name shown in CTA copy. */\n name: string;\n /**\n * Stable identifier used by clients to look up brand icons. The SDK\n * does not ship icon assets — sifa-web maintains them keyed by this\n * id so React Native vs web rendering can diverge.\n */\n iconKey: 'leaflet' | 'pckt' | 'offprint';\n}\n\nexport const STANDARD_SITE_PUBLISHERS: readonly Publisher[] = Object.freeze([\n { host: 'leaflet.pub', name: 'Leaflet', iconKey: 'leaflet' },\n { host: 'pckt.blog', name: 'pckt', iconKey: 'pckt' },\n { host: 'offprint.app', name: 'Offprint', iconKey: 'offprint' },\n]);\n\n/**\n * Returns true when `host` exactly matches `target` or is a subdomain of\n * it. Hosts are expected to be lowercase already.\n */\nexport function hostMatches(host: string, target: string): boolean {\n return host === target || host.endsWith('.' + target);\n}\n\nfunction hostFromUri(uri: string | undefined | null): string | null {\n if (!uri) return null;\n try {\n return new URL(uri).host.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * Match a publisher by host. Returns the publisher when `host` is on the\n * allowlist (exact or subdomain match), null otherwise. Host should be\n * lowercase.\n */\nexport function matchPublisherByHost(host: string | null | undefined): Publisher | null {\n if (!host) return null;\n const lower = host.toLowerCase();\n return STANDARD_SITE_PUBLISHERS.find((p) => hostMatches(lower, p.host)) ?? null;\n}\n\n/**\n * Match a publisher by URI. Convenience wrapper around\n * `matchPublisherByHost` for callers that have a publication URL handy.\n */\nexport function matchPublisherByUri(uri: string | undefined | null): Publisher | null {\n return matchPublisherByHost(hostFromUri(uri));\n}\n","/**\n * Linkblog-share detection for the Standard.site document family.\n *\n * A \"linkblog\" (e.g. Skyreader) writes a `site.standard.document` to the\n * user's PDS for every *external* article they share. Those documents are\n * valid Standard.site posts, so they surface in Sifa's activity stream and\n * profile portfolio as if the user authored them — but the user only shared\n * someone else's writing (sifa-workspace#351).\n *\n * These predicates identify such shares so callers can exclude them, while\n * leaving genuinely authored Standard.site posts (Leaflet, pckt, Offprint\n * essays) untouched.\n *\n * Signals, most to least reliable:\n * 1. The Skyreader provenance marker on the document (`skyreaderLinkblog`).\n * 2. The document's parent publication is itself a linkblog (the marker\n * lives on the publication even when older documents lack their own).\n * Callers resolve the publication and pass `publicationIsLinkblog`.\n * 3. A `links` entry with `rel: \"repost\"` — a quote-reshare, inherently\n * not the user's own writing.\n * 4. Heuristic: a `links` entry with `rel: \"related\"` pointing to an\n * external host different from the document's own publication host —\n * the document's subject is an off-site article. Only applied when the\n * caller supplies the resolved `publicationHost`, so an authored post\n * that merely cites an external resource is never hidden on a guess.\n */\n\n/**\n * Discovery marker Skyreader stamps on every linkblog publication and on\n * link-share documents. Value is a fixed URL, not a per-user value.\n */\nexport const SKYREADER_LINKBLOG_MARKER_URL = 'https://skyreader.app/linkblog';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** A non-empty `skyreaderLinkblog` marker field. */\nfunction hasLinkblogMarker(record: Record<string, unknown>): boolean {\n const marker = record.skyreaderLinkblog;\n return typeof marker === 'string' && marker.length > 0;\n}\n\nfunction hostOf(uri: string): string | null {\n try {\n return new URL(uri).host.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * True when a `site.standard.publication` record is a linkblog rather than an\n * authored publication — i.e. it carries the Skyreader provenance marker.\n */\nexport function isLinkblogPublication(record: unknown): boolean {\n return isRecord(record) && hasLinkblogMarker(record);\n}\n\nexport interface LinkblogShareOptions {\n /**\n * True when the document's parent publication has been resolved and is\n * itself a linkblog (see {@link isLinkblogPublication}). Catches shares\n * whose document predates the per-document marker.\n */\n publicationIsLinkblog?: boolean;\n /**\n * Resolved host of the document's parent publication, enabling the\n * external-subject heuristic. Omit when the publication (hence its host)\n * could not be resolved — the heuristic then stays off.\n */\n publicationHost?: string;\n}\n\n/**\n * True when a `site.standard.document` is a shared/linkblogged external\n * article rather than the user's own writing. See the module comment for the\n * signal order.\n */\nexport function isLinkblogShareDocument(record: unknown, opts: LinkblogShareOptions = {}): boolean {\n if (!isRecord(record)) return false;\n\n // 1. Provenance marker on the document itself.\n if (hasLinkblogMarker(record)) return true;\n\n // 2. Parent publication is a linkblog (caller-resolved).\n if (opts.publicationIsLinkblog === true) return true;\n\n const links = record.links;\n if (!Array.isArray(links)) return false;\n\n const pubHost = opts.publicationHost?.toLowerCase();\n for (const link of links) {\n if (!isRecord(link) || typeof link.rel !== 'string') continue;\n\n // 3. Reshare of another record.\n if (link.rel === 'repost') return true;\n\n // 4. External-subject heuristic (only with a known publication host).\n if (link.rel === 'related' && pubHost && typeof link.uri === 'string') {\n const linkHost = hostOf(link.uri);\n if (linkHost && linkHost !== pubHost) return true;\n }\n }\n\n return false;\n}\n","import type { BasicTheme } from './schemas.js';\n\n/**\n * Publication metadata embedded in the augmented activity view by\n * sifa-api when an external link card matches an indexed publication.\n *\n * `uri` is the publication's AT-URI (e.g.\n * `at://did:plc:abc/site.standard.publication/xyz`). `theme` carries\n * the publication's own colors when supplied — clients should apply a\n * WCAG min-contrast fallback before honoring them.\n *\n * `icon` is left as `unknown` here because its representation depends\n * on the surface: blob ref on the wire, CDN URL after resolution.\n */\nexport interface PublicationSource {\n uri: string;\n title: string;\n icon?: unknown;\n theme?: BasicTheme;\n}\n\n/**\n * Shape attached to activity items whose external link card URL matches\n * a Standard.site record. Mirrors the relevant fields of bsky's\n * `StandardSiteEmbed` view so a sifa-web renderer can be near-1:1.\n *\n * `associatedRefs` carries AT-URIs the client may use in Phase 4 to\n * query subscription state for the viewer.\n */\nexport interface StandardSiteEmbedView {\n uri: string;\n source: PublicationSource;\n associatedRefs: { uri: string }[];\n createdAt?: string;\n readingTime?: number;\n}\n\nexport const STANDARD_SITE_PUBLICATION_NSID = 'site.standard.publication' as const;\nexport const STANDARD_SITE_DOCUMENT_NSID = 'site.standard.document' as const;\nexport const STANDARD_SITE_SUBSCRIPTION_NSID = 'site.standard.graph.subscription' as const;\nexport const STANDARD_SITE_RECOMMEND_NSID = 'site.standard.graph.recommend' as const;\n\n/**\n * Pre-defined OAuth permission-set the consumer apps request to write\n * subscription + recommend records on behalf of the viewer. The actual\n * permission-set is published under\n * `at://did:plc:re3ebnp5v7ffagz6rb6xfei4/com.atproto.lexicon.schema/site.standard.authSocial`.\n */\nexport const STANDARD_SITE_AUTH_SOCIAL_NSID = 'site.standard.authSocial' as const;\n\nconst COLLECTION_SET = new Set<string>([\n STANDARD_SITE_PUBLICATION_NSID,\n STANDARD_SITE_DOCUMENT_NSID,\n STANDARD_SITE_SUBSCRIPTION_NSID,\n STANDARD_SITE_RECOMMEND_NSID,\n]);\n\n/**\n * True when the AT-URI's collection segment is a Standard.site\n * collection. Useful for detecting Standard.site embed augmentation on\n * arbitrary activity items.\n */\nexport function isStandardSiteAtUri(uri: string): boolean {\n if (!uri.startsWith('at://')) return false;\n const segments = uri.slice('at://'.length).split('/');\n const collection = segments[1];\n return collection !== undefined && COLLECTION_SET.has(collection);\n}\n\n/**\n * True when any `associatedRefs[].uri` is a Standard.site collection\n * AT-URI. The bsky client uses the same check to gate the\n * `StandardSiteEmbed` renderer.\n */\nexport function hasStandardSiteAssociatedRef(refs: { uri: string }[] | undefined): boolean {\n if (!refs) return false;\n return refs.some((r) => isStandardSiteAtUri(r.uri));\n}\n"]}
@@ -454,12 +454,16 @@ async function fetchReciprocityCandidate(config, options = {}) {
454
454
 
455
455
  // src/query/fetchers/endorsement-inbox.ts
456
456
  async function fetchPendingEndorsements(config, options = {}) {
457
+ const { cookieHeader, ...rest } = options;
458
+ const headers = { ...rest.headers ?? {} };
459
+ if (cookieHeader) headers.cookie = cookieHeader;
457
460
  try {
458
461
  const data = await apiFetch(config, "/api/endorsements/pending", {
459
462
  cache: "no-store",
460
463
  credentials: "include",
461
464
  timeoutMs: 5e3,
462
- ...options
465
+ ...rest,
466
+ headers
463
467
  });
464
468
  return { endorsements: data?.endorsements ?? [], cursor: data?.cursor };
465
469
  } catch {
@@ -472,12 +476,16 @@ function dismissEndorsement(config, data, options = {}) {
472
476
 
473
477
  // src/query/fetchers/confirmations.ts
474
478
  async function fetchPendingConfirmations(config, options = {}) {
479
+ const { cookieHeader, ...rest } = options;
480
+ const headers = { ...rest.headers ?? {} };
481
+ if (cookieHeader) headers.cookie = cookieHeader;
475
482
  try {
476
483
  const data = await apiFetch(config, "/api/confirmations/pending", {
477
484
  cache: "no-store",
478
485
  credentials: "include",
479
486
  timeoutMs: 5e3,
480
- ...options
487
+ ...rest,
488
+ headers
481
489
  });
482
490
  return { confirmations: data?.confirmations ?? [], cursor: data?.cursor };
483
491
  } catch {
@@ -485,11 +493,14 @@ async function fetchPendingConfirmations(config, options = {}) {
485
493
  }
486
494
  }
487
495
  async function fetchGivenConfirmations(config, options = {}) {
496
+ const { cookieHeader, ...rest } = options;
497
+ const headers = { ...rest.headers ?? {} };
498
+ if (cookieHeader) headers.cookie = cookieHeader;
488
499
  try {
489
500
  const data = await apiFetch(
490
501
  config,
491
502
  "/api/confirmations/mine",
492
- { cache: "no-store", credentials: "include", timeoutMs: 5e3, ...options }
503
+ { cache: "no-store", credentials: "include", timeoutMs: 5e3, ...rest, headers }
493
504
  );
494
505
  return { confirmations: data?.confirmations ?? [] };
495
506
  } catch {
@@ -506,6 +517,90 @@ function revokeConfirmation(config, data, options = {}) {
506
517
  return apiWrite(config, "/api/confirmation/revoke", "POST", { body: data, ...options });
507
518
  }
508
519
 
520
+ // src/query/fetchers/inbox.ts
521
+ function toCount(value) {
522
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
523
+ }
524
+ async function fetchInboxCounts(config, options = {}) {
525
+ const { cookieHeader, ...rest } = options;
526
+ const headers = { ...rest.headers ?? {} };
527
+ if (cookieHeader) headers.cookie = cookieHeader;
528
+ try {
529
+ const data = await apiFetch(config, "/api/inbox/counts", {
530
+ cache: "no-store",
531
+ credentials: "include",
532
+ timeoutMs: 5e3,
533
+ ...rest,
534
+ headers
535
+ });
536
+ return {
537
+ tasks: toCount(data?.tasks),
538
+ unreadNotifications: toCount(data?.unreadNotifications)
539
+ };
540
+ } catch {
541
+ return { tasks: 0, unreadNotifications: 0 };
542
+ }
543
+ }
544
+
545
+ // src/query/fetchers/unlinked-positions.ts
546
+ async function fetchUnlinkedPositions(config, options = {}) {
547
+ const { cookieHeader, ...rest } = options;
548
+ const headers = { ...rest.headers ?? {} };
549
+ if (cookieHeader) headers.cookie = cookieHeader;
550
+ try {
551
+ const data = await apiFetch(config, "/api/positions/unlinked", {
552
+ cache: "no-store",
553
+ credentials: "include",
554
+ timeoutMs: 5e3,
555
+ ...rest,
556
+ headers
557
+ });
558
+ return { positions: Array.isArray(data?.positions) ? data.positions : [] };
559
+ } catch {
560
+ return { positions: [] };
561
+ }
562
+ }
563
+
564
+ // src/query/fetchers/profile-completeness.ts
565
+ var PROFILE_COMPLETENESS_SIGNALS = [
566
+ "headline",
567
+ "about",
568
+ "position",
569
+ "education",
570
+ "skill",
571
+ "certification"
572
+ ];
573
+ async function fetchProfileCompleteness(config, options = {}) {
574
+ const { cookieHeader, ...rest } = options;
575
+ const headers = { ...rest.headers ?? {} };
576
+ if (cookieHeader) headers.cookie = cookieHeader;
577
+ const empty = {
578
+ complete: true,
579
+ score: PROFILE_COMPLETENESS_SIGNALS.length,
580
+ total: PROFILE_COMPLETENESS_SIGNALS.length,
581
+ missing: []
582
+ };
583
+ try {
584
+ const data = await apiFetch(config, "/api/profile/completeness", {
585
+ cache: "no-store",
586
+ credentials: "include",
587
+ timeoutMs: 5e3,
588
+ ...rest,
589
+ headers
590
+ });
591
+ if (!data) return empty;
592
+ const missing = Array.isArray(data.missing) ? data.missing : [];
593
+ return {
594
+ complete: data.complete ?? missing.length === 0,
595
+ score: data.score ?? PROFILE_COMPLETENESS_SIGNALS.length - missing.length,
596
+ total: data.total ?? PROFILE_COMPLETENESS_SIGNALS.length,
597
+ missing
598
+ };
599
+ } catch {
600
+ return empty;
601
+ }
602
+ }
603
+
509
604
  // src/query/fetchers/keytrace-claims.ts
510
605
  function hideKeytraceClaim(config, rkey, options = {}) {
511
606
  return apiWrite(
@@ -1607,11 +1702,23 @@ var sifaQueryKeys = {
1607
1702
  byHandle: (handleOrDid) => ["sifa", "profile", handleOrDid],
1608
1703
  atFundLink: (did) => ["sifa", "profile", "at-fund-link", did],
1609
1704
  view: (actor) => ["sifa", "profile", "view", actor],
1610
- externalAccounts: (handleOrDid) => ["sifa", "profile", "external-accounts", handleOrDid]
1705
+ externalAccounts: (handleOrDid) => ["sifa", "profile", "external-accounts", handleOrDid],
1706
+ // Session-scoped: the AppView reads the owner from the session cookie, so
1707
+ // there is only ever one completeness result per client.
1708
+ completeness: () => ["sifa", "profile", "completeness"]
1611
1709
  },
1612
1710
  position: {
1613
1711
  all: () => ["sifa", "position"],
1614
- byOwner: (did) => ["sifa", "position", "by-owner", did]
1712
+ byOwner: (did) => ["sifa", "position", "by-owner", did],
1713
+ // Session-scoped: the AppView reads the owner from the session cookie.
1714
+ unlinked: () => ["sifa", "position", "unlinked"]
1715
+ },
1716
+ // Aggregate inbox counts for the header bell badge (session-scoped). The first
1717
+ // aggregate namespace: it fans in across confirmations, endorsements, drift,
1718
+ // unlinked positions and profile completeness on the server.
1719
+ inbox: {
1720
+ all: () => ["sifa", "inbox"],
1721
+ counts: () => ["sifa", "inbox", "counts"]
1615
1722
  },
1616
1723
  search: {
1617
1724
  all: () => ["sifa", "search"],
@@ -1750,6 +1857,11 @@ var externalRecordRefSchema = zod.z.object({
1750
1857
  uri: atUriSchema,
1751
1858
  cid: cidSchema.optional()
1752
1859
  });
1860
+ zod.z.object({
1861
+ name: zod.z.string().min(1).refine(maxGraphemes(256)).max(2560),
1862
+ did: didSchema.optional(),
1863
+ entityRef: uriSchema.optional()
1864
+ }).passthrough();
1753
1865
  var selfLabelsSchema = zod.z.object({
1754
1866
  $type: zod.z.literal("com.atproto.label.defs#selfLabels").optional(),
1755
1867
  values: zod.z.array(zod.z.object({ val: zod.z.string() }))
@@ -1860,6 +1972,7 @@ function deleteInvestment(config, rkey, options = {}) {
1860
1972
  exports.ApiError = ApiError;
1861
1973
  exports.HIDDEN_ITEM_SOURCES = HIDDEN_ITEM_SOURCES;
1862
1974
  exports.HIDDEN_ITEM_TYPES = HIDDEN_ITEM_TYPES;
1975
+ exports.PROFILE_COMPLETENESS_SIGNALS = PROFILE_COMPLETENESS_SIGNALS;
1863
1976
  exports.QUOTED_POSTS_BATCH_MAX = QUOTED_POSTS_BATCH_MAX;
1864
1977
  exports.addFeatureAllowlist = addFeatureAllowlist;
1865
1978
  exports.addOrgNotificationEmail = addOrgNotificationEmail;
@@ -1914,6 +2027,7 @@ exports.fetchGivenConfirmations = fetchGivenConfirmations;
1914
2027
  exports.fetchHeatmapData = fetchHeatmapData;
1915
2028
  exports.fetchHiddenActivityItems = fetchHiddenActivityItems;
1916
2029
  exports.fetchHiddenApps = fetchHiddenApps;
2030
+ exports.fetchInboxCounts = fetchInboxCounts;
1917
2031
  exports.fetchMyGithubPullRequests = fetchMyGithubPullRequests;
1918
2032
  exports.fetchMyRoadmapVotes = fetchMyRoadmapVotes;
1919
2033
  exports.fetchNetworkMap = fetchNetworkMap;
@@ -1921,6 +2035,7 @@ exports.fetchNetworkStreamCount = fetchNetworkStreamCount;
1921
2035
  exports.fetchPendingConfirmations = fetchPendingConfirmations;
1922
2036
  exports.fetchPendingEndorsements = fetchPendingEndorsements;
1923
2037
  exports.fetchProfile = fetchProfile;
2038
+ exports.fetchProfileCompleteness = fetchProfileCompleteness;
1924
2039
  exports.fetchProfileSummary = fetchProfileSummary;
1925
2040
  exports.fetchReactionStatus = fetchReactionStatus;
1926
2041
  exports.fetchReceivedEndorsements = fetchReceivedEndorsements;
@@ -1937,6 +2052,7 @@ exports.fetchStats = fetchStats;
1937
2052
  exports.fetchSuggestionCount = fetchSuggestionCount;
1938
2053
  exports.fetchSuggestions = fetchSuggestions;
1939
2054
  exports.fetchTalks = fetchTalks;
2055
+ exports.fetchUnlinkedPositions = fetchUnlinkedPositions;
1940
2056
  exports.fetchWipePreview = fetchWipePreview;
1941
2057
  exports.followUser = followUser;
1942
2058
  exports.getAdminReviewQueues = getAdminReviewQueues;