import { P as ProfileSkill, a as ProfilePresentationDelivery, L as LocationValue, b as PdsProviderInfo, c as Profile, A as AccountFacetMode, d as ProfilePosition, e as ProfileCertification, f as ProfileEducation, g as ProfileHonor, h as ProfileLanguage, i as ProfileProject, j as ProfilePublication, k as ProfileInvolvement } from './index-CTzpbW81.cjs'; export { l as ActiveApp, m as ActorCard, n as AuthorSuggestion, C as CoSpeaker, E as Endorsement, o as EndorsementData, p as ExternalAccount, q as ExternalAccountKeytraceClaim, F as FeedItem, r as LanguageProficiency, O as OrgAddressView, s as OrgFloorVerdict, t as OrgLinkView, u as OrgProfileView, v as PROJECT_ROLES, w as PresentationLinkView, x as ProfileCourse, y as ProfileIndustry, z as ProfileInvestment, B as ProfileInvestmentLink, D as ProfileInvolvementLink, G as ProfileLocation, H as ProfileOverrideSource, I as ProfilePresentation, J as ProfileVolunteering, K as ProjectMemberCard, M as ProjectRole, N as PublicationContributor, R as RecognizedEntity, S as SkillRef, Q as SkillSuggestion, T as TrustStat, V as VerifiedAccount } from './index-CTzpbW81.cjs'; export { A as ADULT_CONTENT_LABELS, a as ActiveAppView, b as ActivityLabel, c as AdultContentLabel, d as AtfundLinkView, C as CertificationView, e as CoSpeakerView, f as ContributorView, g as CourseView, D as DurationView, E as EducationView, h as ExternalAccountView, H as HonorView, I as IndustryDomain, i as InvolvementLinkView, j as InvolvementView, L as LanguageView, P as PositionView, k as PresentationDeliveryView, l as PresentationView, m as ProfileLocationView, n as ProfileView, o as ProjectMemberView, p as ProjectView, q as PublicationView, R as RepoCollectionGroup, r as RepoDeleteOutcome, s as RepoDeleteRecordResult, t as RepoDeleteResult, u as RepoGroupDefinition, v as RepoGroupId, w as RepoInventory, x as RepoRecordLabel, y as RepoRecordSummary, S as SIFA_REPO_GROUPS, z as SkillView, V as VolunteeringView, B as describeSifaRecord, F as hasAdultContent, G as repoGroupForCollection } from './types-DAEkBwRB.cjs'; import { P as PresentationDuration, a as ProfilePresentationDeliveryRecord, b as ProfilePresentationRecord, O as OrgProfileRecord } from './profile-investment-CmiQxk5C.cjs'; export { A as ArtifactLink, c as ArtifactLinkSchema, B as BlobRef, d as BlobRefSchema, E as EndorsementConfirmationRecord, e as EndorsementConfirmationRecordSchema, f as EndorsementRecord, g as EndorsementRecordSchema, G as GraphFollowRecord, h as GraphFollowRecordSchema, I as InvestmentAmountSchema, i as OrgEmploymentAttestationRecord, j as OrgEmploymentAttestationRecordSchema, k as OrgProfileRecordSchema, l as PROFILE_INVESTMENT_NSID, m as PROFILE_INVOLVEMENT_NSID, n as PresentationDurationSchema, o as PresentationLink, p as PresentationLinkSchema, q as ProfileCertificationRecord, r as ProfileCertificationRecordSchema, s as ProfileCourseRecord, t as ProfileCourseRecordSchema, u as ProfileEducationRecord, v as ProfileEducationRecordSchema, w as ProfileExternalAccountRecord, x as ProfileExternalAccountRecordSchema, y as ProfileHonorRecord, z as ProfileHonorRecordSchema, C as ProfileInvestmentRecordSchema, D as ProfileInvolvementRecord, F as ProfileInvolvementRecordSchema, H as ProfileLanguageRecord, J as ProfileLanguageRecordSchema, K as ProfilePositionRecord, L as ProfilePositionRecordSchema, M as ProfilePresentationDeliveryRecordSchema, N as ProfilePresentationRecordSchema, Q as ProfileProjectRecord, R as ProfileProjectRecordSchema, S as ProfilePublicationRecord, T as ProfilePublicationRecordSchema, U as ProfileSelfRecord, V as ProfileSelfRecordSchema, W as ProfileSkillRecord, X as ProfileSkillRecordSchema, Y as ProfileVolunteeringRecord, Z as ProfileVolunteeringRecordSchema, _ as PublicationAuthor, $ as PublicationAuthorSchema, a0 as atUriSchema, a1 as cidSchema, a2 as datetimeSchema, a3 as didSchema, a4 as externalRecordRefSchema, a5 as languageTagSchema, a6 as makeGraphFollowRecordSchema, a7 as maxGraphemes, a8 as partialDateSchema, a9 as selfLabelsSchema, aa as skillRefSchema, ab as strongRefSchema, ac as uriSchema } from './profile-investment-CmiQxk5C.cjs'; import { z } from 'zod'; export { P as ProfileSummary, S as SummarizeProfileViewOptions, s as summarizeProfileView } from './profile-summary-C557t4hr.cjs'; import { E as EntitySearchResult } from './feed-BMm9Pkew.cjs'; export { A as AtmosphereFeedItem, a as AtmosphereFeedItemSchema, b as EntityImportSearchResponse, c as EntityImportSearchResponseSchema, d as EntityMintDomainResponse, e as EntityMintDomainResponseSchema, f as EntityResolveDomainRequest, g as EntityResolveDomainRequestSchema, h as EntityResolveDomainResponse, i as EntityResolveDomainResponseSchema, j as EntitySearchResponse, k as EntitySearchResponseSchema, l as EntitySearchResultSchema, m as EntitySelectRequest, n as EntitySelectRequestSchema, o as EntitySelectResponse, p as EntitySelectResponseSchema, F as FEATURE_FLAGS, q as FeatureAllowlistEntry, r as FeatureAllowlistEntrySchema, s as FeatureFlag, t as FeedActor, u as FeedActorSchema, v as FeedCursor, w as FollowFeedItem, x as FollowFeedItemSchema, y as FollowFeedPage, z as FollowFeedPageSchema, B as FollowProfileItem, C as FollowProfilePage, D as FollowProfilePageSchema, G as FollowProfileSchema, S as SifaFeedItem, H as SifaFeedItemSchema, I as decodeFeedCursor, J as encodeFeedCursor } from './feed-BMm9Pkew.cjs'; type ContinentCode = 'AF' | 'AN' | 'AS' | 'EU' | 'NA' | 'OC' | 'SA'; declare const CONTINENTS: ReadonlyArray<{ code: ContinentCode; name: string; }>; declare function getContinent(countryCode: string): ContinentCode | null; /** ISO 3166-1 country list for local fallback when GeoNames is unavailable. */ declare const COUNTRIES: ReadonlyArray<{ code: string; name: string; }>; /** * Employment-type taxonomy. Mirrors `id.sifa.defs#employmentType.knownValues` * from sifa-lexicons. Surfaces both a flat NSID→label map (for read-only * display) and a grouped structure (for editor dropdowns). */ interface EmploymentTypeOption { value: string; label: string; } interface EmploymentTypeGroup { label: string; items: EmploymentTypeOption[]; } declare const EMPLOYMENT_TYPE_GROUPS: EmploymentTypeGroup[]; declare const EMPLOYMENT_TYPE_LABELS: Record; /** Resolve a label for an employment-type token. Falls back to the raw value. */ declare function getEmploymentTypeLabel(value: string | undefined | null): string | undefined; /** * Employment-type group whose members may omit a company name: independent * workers (contract, freelance, self-employed, independent work) often have no * separately named or registered entity. Derived from the group rather than a * hand-maintained list so it stays in sync if the group's membership changes. */ declare const COMPANY_OPTIONAL_EMPLOYMENT_TYPES: ReadonlySet; /** * Whether a company name is required for a position with the given employment * type. Company is optional for the Independent group; required otherwise, * including when the employment type is unspecified (the conservative default, * since most positions are at a named organization). */ declare function isCompanyRequired(employmentType: string | undefined | null): boolean; /** * Employment types for which the `onBehalfOf` disclosure is meaningful — a seat or * advisory role can be held as someone else's representative, most often a fund whose * board seat it is. Derived from the group rather than hand-listed so it cannot drift. */ declare const ON_BEHALF_OF_EMPLOYMENT_TYPES: ReadonlySet; /** * Whether to offer the `onBehalfOf` disclosure for a given employment type. False when * the type is unspecified: a full-time employee represents nobody, and surfacing the * field on every position form would put a rare disclosure in front of everyone. */ declare function isOnBehalfOfApplicable(employmentType: string | undefined | null): boolean; /** Two-level industry/domain taxonomy for profile classification. */ interface IndustryOption { value: string; labelKey: string; domains: { value: string; labelKey: string; }[]; } declare const INDUSTRY_OPTIONS: IndustryOption[]; /** Find the industry option for a given industry value. */ declare function findIndustry(value: string | undefined | null): IndustryOption | undefined; /** Get the label key for an industry or domain token, falling back to the raw value. */ declare function getIndustryLabelKey(value: string): string; /** * "Open to" taxonomy. Mirrors `id.sifa.defs#openToWorkStatus.knownValues` from * sifa-lexicons. Surfaces a single source of truth for the editor (where the * user picks values) and the IdentityCard (where badges are rendered). * * Shape matches `INDUSTRY_OPTIONS`: `{ value, labelKey }` so consumers can run * `labelKey` through their own i18n layer (e.g. next-intl `useTranslations`). */ type OpenToGroup = 'work' | 'mentorship' | 'peer'; interface OpenToOption { value: string; labelKey: string; token: string; group: OpenToGroup; } declare const OPEN_TO_OPTIONS: OpenToOption[]; /** Map short token (e.g. "fullTime") -> lex value (e.g. "id.sifa.defs#fullTimeRoles"). */ declare const OPEN_TO_TOKEN_TO_VALUE: Record; /** Map lex value -> short token. */ declare const OPEN_TO_VALUE_TO_TOKEN: Record; /** All valid tokens, useful for runtime validation. */ declare const OPEN_TO_TOKENS: readonly string[]; /** Resolve a token to its lex value, returning `undefined` if unknown. */ declare function openToTokenToValue(token: string | undefined | null): string | undefined; /** Resolve a lex value to its token, returning `undefined` if unknown. */ declare function openToValueToToken(value: string | undefined | null): string | undefined; /** * Resolve a `labelKey` for an `openToWorkStatus` token. Returns `undefined` * for tokens not in the canonical set or the legacy alias map, so callers * can choose their own fallback (e.g. render the raw value). */ declare function getOpenToLabelKey(value: string | undefined | null): string | undefined; /** * Normalize a list of `openTo` values: resolve legacy aliases to their * canonical value, then dedup while preserving first-seen order. Unknown tokens * pass through untouched (forward-compat). Used by the editor (so a legacy token * maps onto a real option and migrates forward on save) and by display (so a * record carrying both the legacy and canonical token renders a single badge). */ declare function normalizeOpenTo(values: readonly string[]): string[]; /** * Workplace-type taxonomy. Mirrors `id.sifa.defs#workplaceType.knownValues` * from sifa-lexicons. `remote` is retained for legacy records but the scoped * variants (`remoteLocal`, `remoteRegion`, `remoteGlobal`) are preferred for * new records per the lexicon description. */ interface WorkplaceTypeOption { value: string; label: string; } declare const WORKPLACE_TYPE_OPTIONS: WorkplaceTypeOption[]; declare const WORKPLACE_TYPE_LABELS: Record; /** Resolve a label for a workplace-type token. Falls back to the raw value. */ declare function getWorkplaceTypeLabel(value: string | undefined | null): string | undefined; /** * Normalize a list of workplace-type tokens: resolve legacy aliases to their * canonical token, then dedup while preserving first-seen order. Unknown tokens * pass through untouched (forward-compat). Used by the editor (so a legacy token * maps onto a real option and migrates forward on save) and by display (so a * record carrying both the legacy and canonical token renders a single badge). */ declare function normalizeWorkplaceTypes(values: readonly string[]): string[]; /** * Platform identifiers, labels, and URL helpers used in profile external accounts. * * This file is intentionally data-only -- icon mapping is platform-specific * (React DOM components on web, React Native components on mobile) and lives * in the consumer. */ declare const PLATFORM_LABELS: { readonly bluesky: "Bluesky"; readonly github: "GitHub"; readonly codeberg: "Codeberg"; readonly gitlab: "GitLab"; readonly forgejo: "Forgejo"; readonly gitea: "Gitea"; readonly linkedin: "LinkedIn"; readonly youtube: "YouTube"; readonly twitter: "X (Twitter)"; readonly instagram: "Instagram"; readonly substack: "Substack"; readonly tangled: "Tangled"; readonly dns: "Domain"; readonly website: "Website"; readonly rss: "RSS"; readonly fediverse: "Fediverse"; readonly orcid: "ORCID"; readonly keyoxide: "Keyoxide"; }; type PlatformId = keyof typeof PLATFORM_LABELS; declare function normalizePlatformId(platform: string): string; declare function isKnownPlatform(platform: string): platform is PlatformId; declare function getPlatformLabel(platform: string): string; /** Platforms available in the "Add Links" dropdown. */ declare const PLATFORM_OPTIONS: ReadonlyArray<{ value: PlatformId; label: string; }>; /** * Build a favicon URL for a given site URL using Google's public favicon service. * Returns `null` if the URL cannot be parsed. */ declare function getFaviconUrl(siteUrl: string): string | null; declare const SKILL_CATEGORIES: readonly [{ readonly value: "technical"; readonly label: "Technical"; }, { readonly value: "business"; readonly label: "Business"; }, { readonly value: "creative"; readonly label: "Creative"; }, { readonly value: "interpersonal"; readonly label: "Interpersonal"; }, { readonly value: "industry"; readonly label: "Industry"; }, { readonly value: "community"; readonly label: "Community"; }, { readonly value: "security"; readonly label: "Security"; }]; type SkillCategory = (typeof SKILL_CATEGORIES)[number]['value']; declare const CATEGORY_ORDER: ReadonlyArray; declare const CATEGORY_LABELS: Record; /** * Reduces a `category` value from a skill record to a bare token. * * `id.sifa.profile.skill` publishes its `knownValues` in lexicon-ref form * (`id.sifa.defs#technical`), so a client writing that is following the * lexicon. Sifa's own surfaces store and compare bare tokens (`technical`), and * without this the ref form falls through to the "other" bucket. Both forms are * legal: `knownValues` is advisory in AT Protocol, not a closed enum. * * Normalising is not validating. An unrecognised value is returned as-is, in * lowercase, and callers decide what to do with it (`groupSkillsByCategory` * buckets it as "other"). */ declare function normalizeSkillCategory(value: string | undefined | null): string | undefined; type MergedProfileSkill = ProfileSkill & { mergedRkeys: string[]; positionRkeys?: string[]; }; /** * Collapses skills that resolve to the same name (case-insensitive, trimmed). * * LinkedIn import creates one skill record per (skill, position) pair, so a * profile can have several "Ruby" rows. We surface them as a single chip with * all underlying rkeys exposed via `mergedRkeys` so edit/delete can fan out. */ declare function dedupeSkills(skills: ProfileSkill[]): MergedProfileSkill[]; /** * Groups skills by category in CATEGORY_ORDER, with unknown/empty categories * collected under "other". Within each group: sorted by endorsementCount desc, * then alphabetical by name. Empty groups are omitted. */ declare function groupSkillsByCategory(skills: T[]): [string, T[]][]; /** * Groups skills by their freeform `subCategory` label (#305), for renderers * that present skills in the user's own groups (Frontend, Backend, etc.). * * Labels are matched case-insensitively and surfaced under the first-seen * casing. Groups are ordered alphabetically by label; skills with no * `subCategory` collect under a trailing `null` bucket (omitted when empty). * Within each group: endorsementCount desc, then alphabetical by name. */ declare function groupSkillsBySubCategory(skills: T[]): [string | null, T[]][]; interface SkillDisplayGroups { /** Sub-groups for one category; a `null` label is the unlabelled bucket. */ groups: [string | null, T[]][]; /** * Whether sub-category labels carry information here. False when the category * has fewer than two distinct sub-categories, where a lone label says nothing * the category heading does not already say. */ showLabels: boolean; } interface SkillDisplayGroupsOptions { /** * Put the unlabelled bucket first instead of last. Inline chip rendering * reads fine with it trailing, but in a linear document (markdown, docx, * print) trailing unlabelled entries appear to belong to the sub-heading * above them, so documents want it directly under the category heading. */ ungroupedFirst?: boolean; } /** * Prepares one category's skills for display: the sub-groups, plus whether to * label them (the render rule from #314). * * Shared so the profile, print, markdown and docx surfaces group identically * rather than each re-deriving the threshold. */ declare function groupSkillsForDisplay(skills: T[], options?: SkillDisplayGroupsOptions): SkillDisplayGroups; /** * App-category taxonomy for AT Protocol apps tracked by Sifa. * * Categories group apps by what kind of activity they produce (Posts, Photos, * Code, ...). They drive the pill-size icon shown in `` and the * fallback glyph used in activity-card headers when no app-specific logo * exists. * * This file is intentionally data-only -- icon mapping is renderer-specific * (React DOM Phosphor components on web, React Native components on mobile) * and lives in the consumer. The `phosphorIcon` field is the Phosphor icon * NAME, not a component reference. * * Categories are an internal grouping; we do not surface this taxonomy in * public-facing copy. */ declare const APP_CATEGORIES: { readonly Art: { readonly phosphorIcon: "PaintBrush"; }; readonly Articles: { readonly phosphorIcon: "Article"; }; readonly Chat: { readonly phosphorIcon: "ChatsCircle"; }; readonly Code: { readonly phosphorIcon: "Code"; }; readonly Domains: { readonly phosphorIcon: "Globe"; }; readonly Endorsements: { readonly phosphorIcon: "HandHeart"; }; readonly Events: { readonly phosphorIcon: "CalendarBlank"; }; readonly Impact: { readonly phosphorIcon: "Certificate"; }; readonly Links: { readonly phosphorIcon: "LinkSimple"; }; readonly Lists: { readonly phosphorIcon: "ListBullets"; }; readonly Music: { readonly phosphorIcon: "MusicNote"; }; readonly Pages: { readonly phosphorIcon: "Browser"; }; readonly Pastes: { readonly phosphorIcon: "Clipboard"; }; readonly Photos: { readonly phosphorIcon: "Camera"; }; readonly Places: { readonly phosphorIcon: "MapPin"; }; readonly Posts: { readonly phosphorIcon: "ChatCircle"; }; readonly 'Q&A': { readonly phosphorIcon: "Question"; }; readonly Recipes: { readonly phosphorIcon: "CookingPot"; }; readonly Research: { readonly phosphorIcon: "Path"; }; readonly Reviews: { readonly phosphorIcon: "Star"; }; readonly Slides: { readonly phosphorIcon: "Presentation"; }; readonly Social: { readonly phosphorIcon: "UsersThree"; }; readonly Translations: { readonly phosphorIcon: "Translate"; }; readonly Verification: { readonly phosphorIcon: "Key"; }; readonly Video: { readonly phosphorIcon: "VideoCamera"; }; }; type AppCategoryId = keyof typeof APP_CATEGORIES; declare const APP_CATEGORY_IDS: AppCategoryId[]; declare function isAppCategory(value: string): value is AppCategoryId; declare function getAppCategoryIcon(category: AppCategoryId): string; /** * Maps every AT Protocol app Sifa tracks to its `AppCategoryId`. Single source * of truth for what category an app belongs to; sifa-api and sifa-web both * import from here. * * Add a new app: pick the category whose generic glyph best represents the * activity. If no existing category fits, add a new one to APP_CATEGORIES * rather than introducing a per-app override. */ declare const APP_CATEGORY_MAP: { readonly bluesky: "Posts"; readonly tangled: "Code"; readonly github: "Code"; readonly fediverse: "Posts"; readonly rss: "Articles"; readonly smokesignal: "Events"; readonly flashes: "Photos"; readonly grain: "Photos"; readonly pixl: "Photos"; readonly whitewind: "Articles"; readonly frontpage: "Links"; readonly pastesphere: "Pastes"; readonly standard: "Articles"; readonly aetheros: "Pages"; readonly roomy: "Social"; readonly keytrace: "Verification"; readonly popfeed: "Reviews"; readonly streamplace: "Video"; readonly semble: "Research"; readonly youandme: "Social"; readonly leaflet: "Articles"; readonly colibri: "Social"; readonly collectivesocial: "Lists"; readonly bookhive: "Reviews"; readonly beaconbits: "Places"; readonly passports: "Places"; readonly asq: "Q&A"; readonly spark: "Posts"; readonly nooki: "Social"; readonly atstore: "Reviews"; readonly plyr: "Music"; readonly anisota: "Posts"; readonly atfund: "Endorsements"; readonly crate: "Articles"; readonly atmorsvp: "Events"; readonly opensocial: "Social"; readonly kevara: "Events"; readonly marque: "Domains"; readonly atcr: "Code"; readonly mcp: "Code"; readonly userinput: "Social"; readonly minomobi: "Music"; readonly voxport: "Video"; readonly badges: "Verification"; readonly atvouch: "Endorsements"; readonly watsm: "Code"; readonly plonk: "Pastes"; readonly alternativeproto: "Links"; readonly papili: "Links"; readonly vit: "Code"; readonly lifepo: "Social"; readonly streamthought: "Posts"; readonly waow: "Photos"; readonly lichen: "Pages"; readonly aetherdocs: "Slides"; readonly kich: "Recipes"; readonly margin: "Research"; readonly recipe: "Recipes"; readonly guestbook: "Social"; readonly atmobb: "Social"; readonly chive: "Research"; readonly zeens: "Photos"; readonly locale: "Translations"; readonly pckt: "Posts"; readonly hypercerts: "Impact"; readonly certified: "Endorsements"; readonly impactindexer: "Reviews"; readonly pinksea: "Art"; readonly linkat: "Links"; readonly kipclip: "Links"; readonly statusphere: "Social"; }; type KnownAppId = keyof typeof APP_CATEGORY_MAP; declare function isKnownAppId(appId: string): appId is KnownAppId; declare function categoryForApp(appId: string): AppCategoryId | undefined; type ActivityTier = 'creation' | 'action' | 'filtered'; interface TierMeta { label: string | null; description: string; shownOnPublicProfile: boolean; } interface LexiconEntry { tier: ActivityTier; app?: string; notes?: string; } interface ActivityTaxonomy { version: string; updated: string; tiers: Record; lexicons: Record; } declare const ACTIVITY_TIERS: Readonly; /** * Returns the activity tier for a given AT Protocol NSID. * Returns 'filtered' for unknown/unclassified NSIDs (safe default — won't * leak unknown records to public profile surfaces). */ declare function getActivityTier(nsid: string): ActivityTier; /** * Returns the full lexicon entry (tier + app + notes), or null if unknown. */ declare function getLexiconEntry(nsid: string): LexiconEntry | null; /** * Returns tier metadata (label, description, public visibility). */ declare function getTierMeta(tier: ActivityTier): TierMeta; /** * Returns the taxonomy version + updated date for diagnostics and * version-skew detection between SDK consumers and the canonical taxonomy. */ declare function getActivityTaxonomyVersion(): { version: string; updated: string; }; /** * Presentation-role taxonomy. Mirrors `id.sifa.defs#presentationRole.knownValues` * from sifa-lexicons. The role describes how the person took part in a single * delivery (it can differ between deliveries of the same presentation). */ interface PresentationRoleOption { value: string; label: string; } declare const PRESENTATION_ROLE_OPTIONS: PresentationRoleOption[]; declare const PRESENTATION_ROLE_LABELS: Record; /** Resolve a label for a presentation-role token. Falls back to the raw value. */ declare function getPresentationRoleLabel(value: string | undefined | null): string | undefined; /** * Publication-type taxonomy. Mirrors `id.sifa.profile.publication.type` * `knownValues` from sifa-lexicons. * * The values are ORCID's work-type vocabulary rather than `id.sifa.defs` tokens, * unlike every other enum here. That is deliberate: the vocabulary is ORCID's, * not Sifa's. It is what the data arrives in and what the AppView stores and * serves unmapped, so a synced work round-trips with no translation table. * Minting Sifa tokens would make Sifa the owner of a bibliographic vocabulary it * did not design. COAR Resource Types stays the anchor a consumer maps to. * * The set is open. A publication that reached Sifa from Standard.site, RSS, or a * hand-typed entry need not fit ORCID's list, so an unknown value is kept as * written rather than dropped. */ interface PublicationTypeOption { value: string; label: string; } declare const PUBLICATION_TYPE_OPTIONS: PublicationTypeOption[]; declare const PUBLICATION_TYPE_LABELS: Record; /** * Resolve a label for a publication type. * * An unrecognized value falls back to the generic label rather than being shown * raw: an ORCID work type is a slug, and rendering "spin-off-company" verbatim * on a card reads as a bug. A caller that wants the raw value already has it. */ declare function getPublicationTypeLabel(value: string | undefined | null): string | undefined; /** * Presentation link-type taxonomy. Mirrors * `id.sifa.defs#presentationLinkType.knownValues` from sifa-lexicons. Used to * pick an icon and a default label for a presentation or delivery link. */ interface PresentationLinkTypeOption { value: string; label: string; } declare const PRESENTATION_LINK_TYPE_OPTIONS: PresentationLinkTypeOption[]; declare const PRESENTATION_LINK_TYPE_LABELS: Record; /** Resolve a label for a presentation link-type token. Falls back to the raw value. */ declare function getPresentationLinkTypeLabel(value: string | undefined | null): string | undefined; /** * Display labels for `community.lexicon.calendar.event` mode and status tokens. * A presentation delivery stores the full upstream token (so it stays faithful * to the source event); these maps turn the token into a human label. */ declare const CALENDAR_EVENT_MODE_LABELS: Record; /** Resolve a label for a calendar-event mode token. Falls back to the raw value. */ declare function getCalendarEventModeLabel(value: string | undefined | null): string | undefined; declare const CALENDAR_EVENT_STATUS_LABELS: Record; /** Resolve a label for a calendar-event status token. Falls back to the raw value. */ declare function getCalendarEventStatusLabel(value: string | undefined | null): string | undefined; /** * Involvement-kind taxonomy. Mirrors `id.sifa.defs#involvementKind.knownValues` * from sifa-lexicons. `label` is the picker/select label; `heading` is the * profile section heading a record of this kind renders under (charity records, * including mapped legacy `volunteering`, render under "Volunteering"). */ interface InvolvementKindOption { value: string; label: string; heading: string; } declare const INVOLVEMENT_KIND_OPTIONS: InvolvementKindOption[]; declare const INVOLVEMENT_KIND_LABELS: Record; declare const INVOLVEMENT_KIND_HEADINGS: Record; /** Resolve the select label for an involvement-kind token. Falls back to the raw value. */ declare function getInvolvementKindLabel(value: string | undefined | null): string | undefined; /** * Resolve the display-section heading for an involvement-kind token. Unknown * kinds (including a future token an older client hasn't learned) fall under * "Other" so a record is never dropped from the profile. */ declare function getInvolvementKindHeading(value: string | undefined | null): string; /** * Artifact-link-kind taxonomy. Mirrors the bare-string `knownValues` of * `id.sifa.defs#artifactLink.kind` from sifa-lexicons. Open set: any string is * valid; these are the recognized kinds used to pick an icon and a default label. */ interface ArtifactLinkKindOption { value: string; label: string; } declare const ARTIFACT_LINK_KIND_OPTIONS: ArtifactLinkKindOption[]; declare const ARTIFACT_LINK_KIND_LABELS: Record; /** Resolve a label for an artifact-link-kind value. Falls back to the raw value. */ declare function getArtifactLinkKindLabel(value: string | undefined | null): string | undefined; /** * Verification providers -- the trust roots Sifa recognizes for AT Protocol * `app.bsky.graph.verification` records. * * Framing (see decisions/2026-07-13-third-party-verification-providers.md, D4): * a verification checkmark is ONE low-tier trust layer, not Sifa's headline. * Most of what it certifies is "this account is who they claim to be" -- useful, * and worth carrying because it's an already-recognized network signal, but low * value on its own. It sits among Sifa's other trust signals and never becomes a * score or a gate. This registry only decides which issuers Sifa displays and * with what provenance. * * Data-only: badge colors and icons are consumer concerns (React DOM on web, * React Native on mobile), same split as {@link PLATFORM_LABELS}. Consumers map * a provider id to a token/color. */ type VerificationProviderId = 'bluesky' | 'mu'; /** How Sifa learns about a provider's verifications. */ type VerificationSource = 'bluesky-api' | 'firehose'; interface VerificationProvider { id: VerificationProviderId; /** Human label for the badge popover, e.g. "Bluesky", "mu (Eurosky)". */ label: string; /** * Primary-display priority (D1): when an account holds verifications from * multiple providers, the lowest number wins the single inline badge and the * rest move into the popover. Must be unique across providers. */ priority: number; /** * - `bluesky-api`: sourced from Bluesky's public-API `verifiedStatus`. Bluesky * maintains its own trusted-verifier set, so Sifa keeps no local DID list. * - `firehose`: sourced from the Jetstream, gated on {@link verifierDids}. */ source: VerificationSource; /** * Trusted verifier DIDs for firehose-sourced providers. A verification record * counts only if its ISSUER DID is in this list. Empty for `bluesky-api` * providers (Bluesky's AppView owns that decision). DIDs, not handles: a handle * takeover must never confer verifier status. */ verifierDids: readonly string[]; } /** * A verification held by an account, as surfaced to the UI. `issuerDid` is * present for firehose-sourced providers and absent for `bluesky-api`. */ interface AccountVerification { provider: VerificationProviderId; verifiedAt?: string | null; issuerDid?: string | null; } declare const VERIFICATION_PROVIDERS: Record; declare function isKnownVerificationProvider(id: string): id is VerificationProviderId; declare function getVerificationProvider(id: string): VerificationProvider | undefined; /** * The firehose issuer gate: given the DID that authored an * `app.bsky.graph.verification` record, return the provider it verifies for, or * `null` if the issuer is not a recognized firehose verifier. API-sourced * providers (Bluesky) are never matched here -- their verifications do not enter * through the firehose, so a self-issued record cannot resolve to them. */ declare function resolveVerifierProvider(issuerDid: string): VerificationProviderId | null; /** * D1 primary-provider selection: from all verifications an account holds, pick * the one whose provider has the highest priority (lowest `priority` number) for * the single inline badge. The rest belong in the popover. Verifications from * unknown providers are ignored. Returns `null` when nothing is displayable. */ declare function primaryVerification(verifications: readonly AccountVerification[]): AccountVerification | null; /** * Investment taxonomy. Mirrors `id.sifa.defs#investmentRole`, `#investmentStage` * and `#investmentStatus` from sifa-lexicons. * * These describe a capital position: money the person put in. Roles with duties -- * board seats, advisory work -- are positions, not investments, and carry their own * `employmentType` values instead. */ interface InvestmentOption { value: string; label: string; } /** * How the capital went in. * * General partner is deliberately absent. Running a fund is a role, recorded as an * `id.sifa.profile.position`, and offering it here would invite listing a fund's * entire portfolio as personal cheques. A partner's own commitment to their fund is * a `limitedPartner` entry naming that fund. */ declare const INVESTMENT_ROLE_OPTIONS: InvestmentOption[]; /** The company's funding stage at the time of the investment, not its stage today. */ declare const INVESTMENT_STAGE_OPTIONS: InvestmentOption[]; /** * Where the position stands now. Recording a write-off is showing your work; nothing * in Sifa derives a hit rate or a return from these values. */ declare const INVESTMENT_STATUS_OPTIONS: InvestmentOption[]; declare const INVESTMENT_ROLE_LABELS: Record; declare const INVESTMENT_STAGE_LABELS: Record; declare const INVESTMENT_STATUS_LABELS: Record; /** Resolve a label for an investment-role token. Falls back to the raw value. */ declare function getInvestmentRoleLabel(value: string | undefined | null): string | undefined; /** Resolve a label for an investment-stage token. Falls back to the raw value. */ declare function getInvestmentStageLabel(value: string | undefined | null): string | undefined; /** Resolve a label for an investment-status token. Falls back to the raw value. */ declare function getInvestmentStatusLabel(value: string | undefined | null): string | undefined; /** * Best-effort title-case for a company display name. * * Company names from the PDL crawl are stored all-lowercase ("spryker", * "spryker systems gmbh"), which reads poorly in the UI. This capitalizes them * for display. Names that already carry any uppercase (ROR / Wikidata, e.g. * "University of Testing" or "IBM") are assumed correctly cased and returned * unchanged, so a proper name is never mangled ("University Of Testing"). * * It is intentionally not a canonicalizer: acronyms it can't know about stay * word-cased ("gmbh" -> "Gmbh"). The user's own custom display name is the * escape hatch when the heuristic is wrong. * * Scoped to ASCII-Latin case-bearing strings (decision D8): any name * containing a non-ASCII letter (accented Latin, Cyrillic, Greek, CJK, * Turkish dotted/dotless i, etc) is returned unchanged. Locale-dependent * casing rules (Turkish i/İ vs ı/I is the canonical trap) make naive * `toUpperCase`/`toLowerCase` unsafe outside ASCII, and this formatter has * no reliable way to pick the right locale for an arbitrary company name. * * Known limitation: this does not (and cannot) distinguish an intentional * all-lowercase ASCII wordmark ("adidas", "thyssenkrupp") from a * PDL-lowercased name that should be title-cased ("spryker" -> "Spryker"). * Both are pure ASCII, so both still get title-cased. See sifa-workspace#235. */ declare function formatCompanyName(name: string): string; /** * Normalize the letter-case of a legal-form designator (GmbH, LLC, N.V., B.V., * S.A., ...) appearing as the trailing whole-word token of a company * name. Only that one token is touched; the company name itself is never * recased, since brand-wordmark casing is not deterministic (see * sifa-workspace#235, deliberately distinct from this pass). * * The input's dot and spacing style is preserved verbatim -- dotted vs undotted * (`N.V.` vs `NV`) is a style choice, so the function never adds, removes, or * reorders dots. It only maps `gmbh`/`Gmbh`/`GMBH` -> `GmbH`, `nv` -> `NV`, * `n.v.` -> `N.V.`, and so on. * * Scoped to ASCII-Latin (decision D8, matching `formatCompanyName`): any name * containing a non-ASCII letter is returned unchanged, because locale-dependent * casing (Turkish i/İ) makes naive upper/lower unsafe. Idempotent: normalizing * an already-correct name is a no-op. */ declare function normalizeLegalForm(name: string): string; /** * Build a stable dedup/prefix key for a company name (decision D8). * * NFC-normalizes, case-folds, and strips Latin diacritics so that visually * or logically equivalent spellings collapse to one key: "Nestlé", "Nestle", * and "NESTLE" all produce the same value. Non-Latin scripts (Cyrillic, CJK, * etc) have no Latin diacritics to strip, so they pass through case-folded * and NFC-normalized only. * * This is a pure SDK helper for the in-memory/dedup key. Persisting it as a * dedicated column on the sifa-api side is tracked separately * (sifa-workspace#229) and out of scope here. */ declare function normalizeCompanyKey(name: string): string; /** * Format a date string as a relative time (e.g. "5m ago", "3d ago"). * Returns an empty string for invalid or future dates. */ declare function formatRelativeTime(dateString: string): string; /** * Format a presentation duration for display. A fixed length renders as * "30 min"; a range renders as "20-30 min". Returns undefined when there is no * duration. A range whose bounds are equal, or whose max is missing, renders as * a single value. */ declare function formatPresentationDuration(duration: PresentationDuration | undefined | null): string | undefined; /** * Server-safe timeline date formatters shared by every profile surface. */ /** Format "2007-01" as "Jan 2007", pass through year-only strings. */ declare function formatTimelineDate(dateStr: string): string; /** * Format a date range for display. Handles missing dates, equal start/end, * and ongoing entries (showPresent defaults to true). */ declare function formatDateRange(start?: string, end?: string, showPresent?: boolean): string; /** A compact roll-up of a presentation's deliveries for the collapsed view. */ interface PresentationDeliverySummary { /** Times the talk was actually delivered (cancelled occasions excluded). */ count: number; /** Most recent year among counted deliveries that carry a date. */ recentYear?: number; /** How many counted deliveries were keynotes. */ keynoteCount: number; /** Up to `venueLimit` distinct event names, most-recent first. */ venues: string[]; /** Distinct counted venues beyond the ones listed in `venues`. */ moreVenues: number; } /** * Summarize a talk's delivery history into the signals the profile shows in the * collapsed view: how many times it was given, the most recent year, how many * were keynotes, and a small sample of the most recent distinct venues. * * Cancelled occasions are excluded from every figure: a talk that was booked * then cancelled was not actually delivered, and counting it would inflate the * "given Nx" signal. */ declare function summarizePresentationDeliveries(deliveries: ProfilePresentationDelivery[] | undefined, options?: { venueLimit?: number; }): PresentationDeliverySummary; /** * Sort profile section items from newest to oldest. * * Rules: * - Items with no end date (ongoing/current) sort to the top. * - Primary sort: end date descending (newest first). * - Secondary sort: start date descending (newest first). * - Items with no dates sort to the bottom. * * Date strings are ISO-ish ("2024", "2024-03", "2024-03-15") and * lexicographic comparison works correctly for them. */ interface DateRange { startDate?: string; endDate?: string; current?: boolean; } type DateExtractor = (item: T) => DateRange; declare function sortByDateDesc(items: T[], extract: DateExtractor): T[]; /** * Convenience extractor for items that use `startDate` / `endDate` fields directly. * Used by volunteering, projects, and other sections that still use startDate/endDate. */ declare function dateRangeExtractor(item: T): DateRange; /** * Extractor for lexicon-aligned items that use `startedAt` / `endedAt` fields. * Used by positions and education (current derived from !endedAt). */ declare function lexiconDateExtractor(item: T): DateRange; /** * Convenience extractor for items with a single `date` field. */ declare function singleDateExtractor(item: T): DateRange; /** * Extractor for certifications which use `issueDate` / `expiryDate`. */ declare function certDateExtractor(item: T): DateRange; /** * Reduce a DOI to its bare form: `10.1234/example`, never * `https://doi.org/10.1234/example` and never `doi:10.1234/example`. * * A DOI inside a URL is a link. Bare, it is an identifier, which is what lets * two records describing one publication be matched on it. The lexicon stores * the bare form for that reason. * * People paste the resolver URL, because that is what a publisher page and a * citation manager both hand you, and citations carry the `doi:` scheme. * Normalizing on the way in is the difference between a field that dedupes and * a field holding three spellings of one value. * * Input that is not a DOI at all is trimmed and returned as-is. This is a * normalizer, not a validator: rejecting here would fail a write over a field * nothing depends on, and older registrants produce shapes worth not policing. */ declare function normalizeDoi(value: string): string; /** * Sanitize raw user input into a valid AT Protocol identifier (handle or DID). * * Handles common mistakes: * - Pasting a full Bluesky profile URL * - Pasting a raw HTTPS URL to a PDS domain * - Using an at:// URI * - Prefixing with @ * - Entering a bare username without .bsky.social */ declare function sanitizeHandleInput(raw: string): string; /** * Format a LocationValue for display. * * Prefers `locality` (the community.lexicon.location.address field) and * falls back to the legacy `city` slot so values produced before the * sifa-api alias migration still render correctly during the additive * transition window. */ declare function formatLocation(loc: LocationValue | null | undefined): string; /** Parse a display string back into a LocationValue (best-effort for legacy data) */ declare function parseLocationString(str: string): LocationValue | null; /** Convert ISO 3166-1 alpha-2 country code to flag emoji */ declare function countryCodeToFlag(code: string | undefined): string; type LocationSegmentKind = 'locality' | 'region' | 'country'; interface LocationSegment { kind: LocationSegmentKind; /** Text to render for this segment. */ label: string; /** Search URL for this segment, or null when it should render as plain text. */ searchHref: string | null; } interface LocationSegmentsOptions { /** * Whether the country is one the search index can filter on. Profiles * imported from LinkedIn sometimes hold a metro area ("Greater Bielefeld * Area") in the country field; linking one promises a result set that does * not exist, so those render as plain text. Defaults to true. */ searchable?: boolean; } /** * Split a location into the ordered segments shown on a profile, marking which * ones link to a search. * * Only the country links today. Region is the weakest of the three (free text, * no search filter) and locality has no filter yet, so both render as plain * text until those land. * * A segment equal to its neighbour is dropped: capital regions produce * "Oslo, Oslo, Norway", and repeating the word helps nobody. Blank segments are * dropped with their separator so nothing renders as "Oslo, , Norway". */ declare function locationSegments(loc: LocationValue | null | undefined, options?: LocationSegmentsOptions): LocationSegment[]; interface PdsProvider { name: string; profileUrl: string; host?: string; } declare function pdsProviderFromApi(apiProvider: PdsProviderInfo | null | undefined, handle: string): PdsProvider | null; declare function getHandleStem(handle: string): string; declare function getDisplayLabel(displayName: string | undefined, handle: string): string; declare function getPdsDisplayName(providerName: string): string; declare function detectPdsProvider(handle: string): PdsProvider | null; /** * Resolve a slide / video URL to an embed descriptor. Pure and offline. * * Three outcomes: * - `iframe`: the URL carries everything an embed needs, so the iframe `src` is * known offline (YouTube, Vimeo, Google Slides, Loom, Canva). * - `oembed`: the URL is a known embeddable provider whose iframe `src` is NOT * in the URL and needs a server-side oEmbed lookup (SlideShare, SpeakerDeck * public deck URLs). The caller resolves the src via its own oEmbed step. * - `link`: not embeddable; render a plain link. */ /** * A resolved embed: an iframe whose src is known offline, an `oembed` provider * whose src needs a server lookup (carries the original page URL), or a plain * link. */ type EmbedResult = { kind: 'iframe'; provider: string; src: string; aspectRatio: '16:9' | '4:3'; } | { kind: 'oembed'; provider: string; aspectRatio: '16:9' | '4:3'; pageUrl: string; } | { kind: 'link'; }; /** * Extract a YouTube video id from any recognized YouTube URL (`youtu.be/ID`, * `youtube.com/watch?v=ID`, `/embed/ID`, `/shorts/ID`), or null. Pure/offline. */ declare function youtubeVideoId(url: string): string | null; /** * The public thumbnail URL for a YouTube video id (`i.ytimg.com`). Derivable * offline. NOTE: this host is Google — never load it directly in a privacy * context; fetch and re-serve it from your own origin. */ declare function youtubeThumbnailUrl(videoId: string): string; /** * Recognize an embeddable URL and return its iframe descriptor, else a link. * Only https URLs are accepted. */ declare function resolveEmbed(url: string): EmbedResult; /** * Slug helpers for talk-page URLs. Pure and offline: a talk page lives at a * path like `/{slug}-{rkey}`, where the slug is a human-readable, SEO-friendly * rendering of the title and the rkey is the record's stable identifier. * Because TIDs never contain a `-`, the rkey can always be recovered as the * substring after the final `-`. */ /** * Render a title as a URL slug: lowercased, diacritics stripped, runs of * non-alphanumerics collapsed to a single `-`, trimmed, and capped at 60 chars. * Returns `''` for an empty or whitespace-only title. */ declare function slugifyTitle(title: string): string; /** * Build the talk-page path segment `${slug}-${rkey}`, or just `rkey` when the * title produces an empty slug. */ declare function buildTalkSlug(title: string, rkey: string): string; /** * Recover the rkey from a talk-page segment: the substring after the final `-` * (TIDs contain no `-`). Returns the whole segment when there is no `-`. */ declare function parseTalkRkey(segment: string): string; /** * Meta description for a profile page. * * Ported from sifa-web so the description and the JSON-LD `jobTitle` are * derived from one place. They previously used different rules for "which * position is current" and could name different roles on the same page. */ interface MetaDescriptionPosition { readonly company?: string; /** * Current canonical name of the linked company entity, resolved by the * AppView at read time. Preferred over the free-text `company` snapshot so a * corrected company name (and a decoded one) shows here, not the value frozen * into the position record. */ readonly entityName?: string; readonly title?: string; readonly startedAt?: string; readonly endedAt?: string; readonly primary?: boolean; readonly hidden?: boolean; } interface MetaDescriptionInput { readonly handle: string; readonly displayName?: string; readonly headline?: string; readonly location?: LocationValue | null; readonly positions?: readonly MetaDescriptionPosition[]; } declare function buildMetaDescription(profile: MetaDescriptionInput): string; /** * Limit runs of Unicode combining marks (`\p{M}`) to at most `maxPerBase` * marks following any single base character. Defuses "Zalgo" text where * dozens of stacked combining marks render outside the line box and bleed * into neighbouring UI. * * `maxPerBase` defaults to 4 — high enough to preserve legitimate stacks * in Thai, Arabic, Vietnamese, and IPA, low enough to neutralise the * vertical-overflow attack vector. */ declare function limitCombiningMarks(value: string, maxPerBase?: number): string; /** * Sanitise untrusted display text from PDS records before rendering in UI: * - strips bidi formatting controls (LRM/RLM/LRE/RLE/PDF/LRO/RLO/LRI/RLI/FSI/PDI) * that can hijack reading order or crash `next/og` (see Satori LRM+emoji bug) * - limits stacked combining marks (Zalgo defence) * * Preserves ZWJ (U+200D) so emoji sequences keep rendering. */ declare function sanitizeDisplayText(value: string, maxCombiningPerBase?: number): string; /** * Truncate a string to at most `maxLen` grapheme clusters, appending an * ellipsis when the string was shortened. Grapheme-aware so it never splits * emoji sequences (ZWJ, regional indicators, combining marks, surrogate pairs). */ declare function truncateGraphemes(value: string, maxLen: number): string; /** * How much of the URL path to keep in the display string. * * - `full`: keep the whole pathname (the path is identity, e.g. * `github.com/user/repo` or `mastodon.social/@handle`). * - `firstSegment`: keep only the first path segment (`gui.do/events`) -- enough * to say "which page" without the deep-slug / marketing cruft. * - `none`: host only (`gui.do`). */ type UrlPathPolicy = 'full' | 'firstSegment' | 'none'; interface FormatDisplayUrlOptions { path?: UrlPathPolicy; } interface DisplayUrl { /** The cleaned label to show (no scheme, no `www.`, no query/hash). */ display: string; /** The full, navigable URL for an `` / hover title. */ href: string; } /** * Canonical URL display formatter, shared by every surface that shows a link * (profile projects, external accounts, identity-card website). Produces a tidy * label plus the real destination, so consumers never re-implement the strip. * * Rules: drop the scheme and a leading `www.`, drop the query string and hash, * drop a trailing slash, and apply the `path` policy. `display` is the label; * `href` is the full canonical URL (scheme + query + hash preserved) for the * anchor and its hover `title`. * * Truncation/ellipsis is deliberately NOT handled here -- it is a view concern * that depends on the rendered width, so consumers truncate with CSS * (`text-overflow: ellipsis`) and keep the full value in `href`/`title`. */ declare function formatDisplayUrl(url: string, options?: FormatDisplayUrlOptions): DisplayUrl; /** * Format a date as a relative time string (e.g. "2 minutes ago", "3 days ago"). */ declare function formatDistanceToNow(date: Date): string; /** * WCAG 2.2 contrast ratio utilities for validating publication theme colors. * Implements the relative luminance and contrast ratio formulas from * https://www.w3.org/TR/WCAG22/#dfn-relative-luminance */ interface RgbColor { r: number; g: number; b: number; } /** * Return true when the value is an RgbColor with all channels as integers in [0, 255]. * Use this before passing untrusted data to the luminance / contrast functions. */ declare function isValidRgbColor(value: unknown): value is RgbColor; /** * Serialize an RgbColor to a CSS `rgb(...)` string. * Channels are floored to integers to prevent sub-pixel drift. */ declare function rgbToString(color: RgbColor): string; /** * Calculate relative luminance of an sRGB color per WCAG 2.2. * Input channels must be numbers in [0, 255]. */ declare function relativeLuminance(color: RgbColor): number; /** * Calculate WCAG 2.2 contrast ratio between two colors. * Returns a value between 1 (identical) and 21 (black/white). */ declare function contrastRatio(color1: RgbColor, color2: RgbColor): number; /** * Check if two colors meet WCAG 2.2 AA contrast requirement (4.5:1 for normal text). */ declare function meetsContrastAA(foreground: RgbColor, background: RgbColor): boolean; /** * CSV parsers for the Talks & sessions import (two templates joined by a * presentation_key). The value parsers and row mappers are reusable and pure; * the actual PDS writing (create a presentation, then write its deliveries with * a presentationRef to it) is orchestrated by the consumer, which has the * created record's AT-URI. */ /** A CSV row as a header-keyed map. Missing cells may be undefined or empty. */ type CsvRow = Record; /** * Parse a free-text duration into minutes: a single value ("30 min", "30 * minutes", "30") or a range ("20-30 min", "20 to 30 minutes", "20-30"). * Returns undefined when no usable number is present. A second number is used * as the upper bound only when it is greater than or equal to the first. */ declare function parsePresentationDuration(input: string | undefined): PresentationDuration | undefined; /** Build a duration from explicit min/max minute columns. */ declare function durationFromMinutes(minRaw: string | undefined, maxRaw: string | undefined): PresentationDuration | undefined; /** Split a delimited list into trimmed, non-empty values. Default delimiter ";". */ declare function parseIntendedAudiences(input: string | undefined, delimiter?: string): string[]; /** * Strip HTML to plain text and decode the common entities, for descriptions * exported as HTML (e.g. Webflow). The AppView re-sanitizes on write; this is * only to produce readable text. */ declare function stripHtmlToText(input: string | undefined): string; /** * Map a friendly role value to its token. An existing token passes through. * Free-text or compound values (e.g. "Organizer & co-host/moderator") are * mapped to the nearest token by keyword. An organizer-only value has no * speaking token, so it, and any other unrecognized value, is dropped rather * than stored raw -- matching normalizePresentationMode's drop-unknown * behavior, so a fixed role dropdown never has to render arbitrary strings. */ declare function normalizePresentationRole(value: string | undefined): string | undefined; /** Map a friendly mode value to the community calendar token; drops an unknown value. */ declare function normalizePresentationMode(value: string | undefined): string | undefined; interface ParsedPresentation { /** presentation_key used to link delivery rows to this presentation. */ key?: string; record: Omit; } /** Map a row of the presentations template to a presentation record (minus createdAt). */ declare function presentationCsvRowToRecord(row: CsvRow): ParsedPresentation; interface ParsedDelivery { /** presentation_key to resolve into a presentationRef once the presentation exists. */ presentationKey?: string; record: Omit; } /** Map a row of the deliveries template to a delivery record (minus createdAt and presentationRef). */ declare function presentationDeliveryCsvRowToRecord(row: CsvRow): ParsedDelivery; /** * URL patterns per app, mirroring the sifa-web registry. * * These describe how to build a clickable URL for an activity card given * the author's handle/did and the record rkey. The patterns are templates * with `{handle}`, `{did}`, and `{rkey}` placeholders; each variable is * URI-encoded when interpolated. * * Tier semantics: * - `urlPattern`: per-item URL (a specific post/event/repo). Preferred. * - `profileUrlPattern`: per-user profile URL on the app. Fallback. * * Apps without either pattern are not clickable. */ interface AppUrlPatterns { urlPattern?: string; profileUrlPattern?: string; } declare const APP_URL_PATTERNS: Readonly>; /** * Map collection NSID prefixes to app ids. Order matters: longer / more * specific prefixes must come before broader ones. */ declare const COLLECTION_TO_APP: ReadonlyArray; /** * An activity item as consumed by the Sifa activity-card components and * the sifa-api external-URL health scanner. The shape mirrors the props * the cards already use, so the resolver returns the same URL the UI * renders. */ interface ActivityItemForUrl { /** Collection NSID, e.g. "sh.tangled.graph.repo". */ collection: string; /** The raw record from the PDS / API. */ record: Record; /** Full at-uri of the record (used by collections that derive URL from uri). */ uri: string; /** Record key. */ rkey: string; /** DID of the author of the record. */ authorDid: string; /** Optional handle (cards prefer handle in URL paths when available). */ authorHandle?: string; } /** * Map a collection NSID to its app id, mirroring the sifa-web prefix map. * * Falls back to the first two NSID segments (e.g. "org.hyperboards") when * the prefix is not registered, matching the existing card behaviour. */ declare function getAppIdForCollection(collection: string): string; /** * How the activity-card health scanner should verify a card is still live. * * - `record`: the URL is a first-party permalink that renders THIS record * (e.g. a Bluesky post at bsky.app, a tangled repo, a smokesignal event). * The authoritative liveness check is "does the record still exist on its * PDS" — `com.atproto.repo.getRecord` on the item's own at-uri — NOT an * HTTP probe of the rendering app. Rendering apps (bsky.app, tangled.sh) * frequently answer HEAD with 404/405 while serving GET fine, which * false-positives every permalink as broken. * - `url`: the URL is a foreign or derived target — a bookmarked page, an * external publisher, a *different* record's page, or a profile page. * Its reachability is independent of this record, so probe the URL. * - `none`: the card has no clickable URL; nothing to check. */ type CardHealthStrategy = 'record' | 'url' | 'none'; interface CardHealth { /** The clickable URL the card renders. Null when non-clickable. */ url: string | null; /** How the scanner should verify liveness. See {@link CardHealthStrategy}. */ strategy: CardHealthStrategy; } /** * Resolve the canonical clickable URL for an activity item **and** how its * liveness should be checked — the single source of truth shared by: * - sifa-web activity cards (which render `.url`), and * - the sifa-api link-health scanner (which switches on `.strategy`: probe the * record's PDS for `record`-strategy cards, probe the URL for `url`). * * See {@link CardHealthStrategy} for why self-permalinks (bsky posts, etc.) * must be verified by record existence rather than an HTTP probe of the app. * * Resolution order: * 1. Per-collection bespoke logic (tangled, kipclip, margin, smokesignal * rsvp, standard documents). These mirror the inline logic in the * individual card components. * 2. Generic `record.url` field (used by hyperboards and similar). * 3. Pattern-based per-item / profile URL from the registry. */ declare function resolveCardHealth(item: ActivityItemForUrl): CardHealth; /** * Resolve the canonical clickable URL for an activity item — the same URL * the activity-card UI in sifa-web renders. Thin wrapper over * {@link resolveCardHealth}; returns `null` when no link is appropriate. */ declare function resolveCardUrl(item: ActivityItemForUrl): string | null; /** * Per-collection visibility rules for activity items. * * These rules express "this record has nothing meaningful to render as a card" * — e.g. a BookHive shelf-add with no review/stars, or a BeaconBits pin with * no shout. They are a UX/card concern, distinct from the api-side blocklists * in sifa-api/atproto-app-registry that exclude whole collections (likes, * follows, games) or filter spammy record shapes (empty quote posts). * * Used in two places: * - sifa-api's activity feed pagination, so a page-of-N returns N visible * items rather than silently-dropped placeholders. * - sifa-web's activity-feed component (and as defense-in-depth in the card * components themselves), so the client never relies on a stale api. * * Adding a new rule = one PR to this file. Unknown collections default to * visible, so the rule set is purely additive — adding a rule can only ever * hide more, never expose what was hidden before. */ type VisibilityPredicate = (record: Record) => boolean; declare const ACTIVITY_VISIBILITY_RULES: Readonly>; /** * Return true if the activity item should render a card in the timeline. * Records that fail the rule for their collection are dropped silently — * they exist in the user's PDS but carry no card-worthy content. * * Unknown collections (no registered rule) default to visible. */ declare function isVisibleActivityItem(collection: string, record: unknown): boolean; /** * Publisher registry — single source of truth for apps that publish documents * through the shared `site.standard.*` namespace (Leaflet, Pckt, Offprint, …). * * Why this exists: * A document published via Leaflet, Pckt, Offprint, etc. lives canonically as a * `site.standard.document` record. The publisher is metadata, identifiable from * the publication's hostname. The activity-card, the publication merger in * sifa-api, and the external-URL health scanner all need the same hostname → * publisher mapping. Keeping it in one place stops the registry from drifting. * * What lives here: * - `id` — stable appId, also keys sifa-web's branding (logo, color) * - `name` — display name * - `hostSuffixes` — hostnames that identify a publication URL as this publisher * - `homeUrl` — last-resort URL when no per-document URL is available * * What does NOT live here: * - Logo components, color tokens, React chrome — those belong in sifa-web * keyed by `id`. * - Per-document URL construction — Standard documents are always * `${publication.url}${document.path}`. The publisher doesn't change that. */ interface Publisher { /** * Stable identifier. Used as the `appId` across Sifa so that sifa-web's * `getAppMeta(id)` lookup yields the right logo and color, and so that * activity-stat rows tag correctly. */ id: string; /** Display name shown in pills and headers. */ name: string; /** * Hostnames that identify a publication URL as belonging to this publisher. * Matches the publication URL's hostname when it equals the suffix * (e.g. `leaflet.pub`) OR ends with it as a dotted suffix * (e.g. `notesbyarielm.leaflet.pub`). * * Allow multiple entries to future-proof against rebrands or aliases. */ hostSuffixes: readonly string[]; /** Public homepage of the publisher, used as a profile-level fallback URL. */ homeUrl: string; } /** * Neutral fallback used when no branded publisher matches. * * Synthetic publishers returned by `getPublisherFromSiteUrl` use this `id` but * carry the document's own hostname in `name`, so cards can still show e.g. * "feeds.byarielm.fyi" as the publisher label. */ declare const STANDARD_PUBLISHER_ID: "standard"; declare const PUBLISHERS: readonly Publisher[]; /** Lookup a registered publisher by id. Returns `undefined` for unknown ids. */ declare function getPublisherById(id: string): Publisher | undefined; /** * Match a hostname against the registry. Returns the registered publisher when * the hostname equals or is a dotted-subdomain of any of its `hostSuffixes`. * Returns `undefined` when no match is found (caller decides on the fallback). */ declare function getPublisherByHost(hostname: string): Publisher | undefined; /** * Resolve the publisher for a publication's site URL. * * - On a branded host: returns the registered `Publisher` verbatim. * - On any other valid URL: returns a synthetic neutral publisher with * `id: 'standard'`, `name: `, `hostSuffixes: []`, and the URL * as `homeUrl`. Cards can therefore always render a meaningful pill label. * - On an unparseable string: returns the same neutral shape with the raw * input as `name` and `homeUrl` (defensive — should never happen for data * coming from a valid `site.standard.publication`). */ declare function getPublisherFromSiteUrl(siteUrl: string): Publisher; /** * The activity envelope the AppView emits and the SDK transform consumes. * * This mirrors the public fields of `sifa-api`'s `ActivityItem` * (`src/routes/activity/types.ts`) so the SDK never depends on the private * api package. Only the fields the shared view-model transform reads are * modelled here; api-only enrichments (quoted-post resolution, standard-site * embeds, content labels) are layered on in sifa-web and are not part of the * shared contract. */ interface HypercertContributorView { displayName: string; imageUrl?: string; /** * Where the contributor lives. A GitHub profile URL on every * `contributorInformation` record sampled to date, never a DID or atproto * handle, so this does not resolve to a Sifa profile. */ identifier?: string; /** Percentage share of the claim; weights across a claim sum to 100. */ weight?: number; } interface HypercertAttachmentView { title?: string; url: string; } /** * Contributor and attachment detail for an `org.hypercerts.claim.activity` * record, resolved by sifa-api out of sidecar records in the claim's own repo. * A card cannot reach them itself. */ interface HypercertDetailsView { contributors: HypercertContributorView[]; /** Total before truncation, so a renderer can show "+N more". */ contributorCount: number; attachments: HypercertAttachmentView[]; } /** An account a Certified record points at: a badge recipient, or a group. */ interface CertifiedActorView { did: string; /** * The account's handle. Falls back to the DID when nothing resolved. Note * that some accounts in this ecosystem are not on Bluesky at all (the * Hypercerts Foundation's own account resolves only through its DID * document), so a Bluesky-only lookup is not sufficient. */ handle: string; displayName?: string; avatar?: string; } /** The badge definition behind an award: what the badge is, and why. */ interface CertifiedBadgeView { title: string; /** Why the badge exists, straight from the definition record. */ description?: string; /** Emoji from the definition, e.g. a medal. */ icon?: string; /** e.g. "endorsement", "evaluator". */ badgeType?: string; } /** * Certified enrichment, resolved by sifa-api. Covers both directions: a badge * the person awarded to someone else, and one they received and accepted. */ interface CertifiedDetailsView { badge?: CertifiedBadgeView; /** Who received the badge, for an award the person issued. */ recipient?: CertifiedActorView; /** Who issued the badge, for one the person received and accepted. */ issuer?: CertifiedActorView; /** The group joined, for a membership record. */ group?: CertifiedActorView; /** Role on a membership, e.g. "owner", "admin", "member". */ role?: string; } interface ActivityItem { /** Full at-uri of the record: stable key + permalink source. */ uri: string; /** Content identifier of the record version. */ cid: string; /** Collection NSID, e.g. `app.bsky.feed.post`. */ collection: string; /** Record key. */ rkey: string; /** The raw record from the PDS / AppView. Parsed defensively by the transform. */ record: unknown; /** * The record author's handle, injected by the AppView. The activity snapshot * is per-author, so the api sets this per item (and per `subject` item) to let * handle-keyed apps (Bluesky, Popfeed, Tangled, ...) resolve a `sourceUrl`. * Absent when the handle is unknown; the transform then relies on the DID. */ authorHandle?: string; /** * The record author's display name, injected by the AppView. Set on `subject` * items (the quoted / reposted / replied-to original post) so the nested card * can show whose post it is. Absent when unknown. */ authorDisplayName?: string; /** * The record author's fully-resolved avatar URL, injected by the AppView. * Like {@link authorDisplayName}, chiefly set on `subject` items. */ authorAvatar?: string; /** App id from the app registry, e.g. `bluesky`. */ appId: string; /** Human app name from the app registry, e.g. `Bluesky`. */ appName: string; /** App category from the app registry, e.g. `Posts`. */ category: string; /** When the AppView indexed the record (ISO 8601). */ indexedAt: string; /** * Repost / reply target, hydrated by the AppView. When present it is * normalized recursively through the same transform so a card's subject is * a `StreamCardVM` too. */ subject?: ActivityItem; /** * Hypercert claim enrichment, set by sifa-api. Absent for non-hypercert * items, for claims with neither contributors nor attachments, and for * responses predating the field. */ hypercertDetails?: HypercertDetailsView; /** * Certified enrichment: the badge behind an award and the resolved accounts * it points at. Absent for non-Certified items and legacy responses. */ certifiedDetails?: CertifiedDetailsView; } /** * The human action a stream item represents — a layer on top of `collection` * + `tier`. Groups semantically across apps ("published" spans WhiteWind and * standard-site; "posted" spans Bluesky and Picosky) so a day-grouped feed * reads like sentences. */ declare const STREAM_VERBS: readonly ["posted", "reposted", "published", "presented", "endorsed", "joined", "shipped", "reviewed", "created"]; type StreamVerb = (typeof STREAM_VERBS)[number]; /** Zod enum for a {@link StreamVerb}, shared with the view-model schema. */ declare const streamVerbSchema: z.ZodEnum<{ created: "created"; posted: "posted"; reviewed: "reviewed"; reposted: "reposted"; published: "published"; presented: "presented"; endorsed: "endorsed"; shipped: "shipped"; joined: "joined"; }>; interface ActivityVerbMap { version: string; updated: string; /** Verb returned for any collection not present in `verbs`. */ defaultVerb: StreamVerb; /** Verb keyed by lexicon NSID. */ verbs: Record; } /** * The verb map, keyed by lexicon NSID. Versioned independently from * `activity-tiers.json` so tier and verb evolve on their own cadence. */ declare const ACTIVITY_VERBS: Readonly; /** * Returns the {@link StreamVerb} for an AT Protocol collection NSID. Unknown * or empty collections fall back to the map's `defaultVerb` (`created`). */ declare function verbForCollection(collection: string): StreamVerb; /** Verb-map version + updated date, for diagnostics and version-skew checks. */ declare function getActivityVerbsVersion(): { version: string; updated: string; }; /** * The app a stream item originated from, resolved from the app registry. * `color` is a design-token NAME (e.g. `slate`), never a hex literal — each * surface maps the token to a concrete value. */ interface StreamSource { appId: string; label: string; color: string; } /** * The record author's identity, when the AppView supplies it. Rendered on * nested subject cards — the quoted / reposted / replied-to original post shows * whose post it is (avatar + name). Top-level cards are the profile owner's own * activity, so surfaces render the owner's identity elsewhere and omit this. * All fields optional: the transform includes whatever the AppView knew. */ interface StreamAuthor { /** Authoring DID, parsed from the record uri when it is an at-uri. */ did?: string; handle?: string; displayName?: string; /** Fully-resolved avatar URL from the AppView (not a raw blob ref). */ avatar?: string; } /** * A per-item theme distinct from the app-registry `source.color`. Publication * and standard-site cards carry a record-level RGB theme (WCAG-AA validated * before use). Absent for most items. */ interface StreamTheme { background: RgbColor; foreground: RgbColor; accent: RgbColor; } /** Fields shared by every media variant. */ interface StreamMediaBase { alt: string; aspectRatio?: { width: number; height: number; }; mimeType?: string; } /** A media item whose URL is already resolved (e.g. a poster URL). */ interface StreamMediaResolved extends StreamMediaBase { url: string; } /** * A media item carried as a raw blob ref. The transform stays host-agnostic * and pure — it does NOT build CDN URLs. Each surface constructs the URL from * `did` + `cid` itself (sifa-web via `buildBlobUrl`, page.sifa.id via its own * CDN base). */ interface StreamMediaBlob extends StreamMediaBase { did: string; cid: string; } type StreamMedia = StreamMediaResolved | StreamMediaBlob; /** An outbound link card (Bluesky external embed, bookmark subject, etc.). */ interface StreamExternalLink { url: string; title?: string; thumb?: string; } /** * One span of a rich-text body. Built from `app.bsky.richtext.facet`-style * facets (byte-offset ranges over the plain `text`). Plain runs carry only * `text`; enriched runs additionally carry exactly one of `link` (resolved * URL), `mention` (DID), or `tag` (hashtag). `body.text` always holds the full * plain string, so a renderer can ignore `richSegments` and still show content. */ interface StreamRichSegment { text: string; link?: string; mention?: string; tag?: string; } /** A geo coordinate pair (beacon `location`). */ interface StreamGeo { latitude: number; longitude: number; } /** A structured postal address (beacon `addressDetails`). All parts optional. */ interface StreamAddress { name?: string; street?: string; locality?: string; region?: string; country?: string; postalCode?: string; } /** * The primary content body. A small discriminated union so both the React and * the string-HTML renderer switch on one field. `track` is reserved (dame * music scrobbles; no sifa-web card uses it yet). The app-specific variants * (`github-pr`, `book`, ...) carry only structured data — enums, raw NSIDs, * dates, blob/URL refs — never pre-localized strings or built URLs, so each * surface renders identically. */ type StreamCardBody = { kind: 'text'; text: string; /** Facet-derived spans over `text` (links/mentions/tags). Additive. */ richSegments?: StreamRichSegment[]; /** Hashtags carried alongside the text (e.g. asq questions). */ tags?: string[]; } | { kind: 'media'; text?: string; tags?: string[]; } | { kind: 'link'; text?: string; tags?: string[]; } | { kind: 'track'; text?: string; trackTitle?: string; artist?: string; } | { kind: 'generic'; text?: string; tags?: string[]; } | { kind: 'github-pr'; repoOwner: string; repoName: string; prNumber: number; title: string; url?: string; /** GitHub language name (renderer maps to a color dot). */ language?: string; additions: number; deletions: number; /** The card's display date is `mergedAt`, not `createdAt`. */ mergedAt?: string; } | { kind: 'book'; title: string; authors: string[]; /** Rating on the lexicon's 1-10 scale. */ stars?: number; /** Raw reading-status NSID, e.g. `buzz.bookhive.defs#finished`. */ status?: string; review?: string; } | { kind: 'media-review'; /** Which popfeed collection this came from (drives the action label). */ reviewKind: 'review' | 'post' | 'note' | 'other'; title?: string; /** Raw creative-work type, e.g. `movie` (renderer maps to a label + icon). */ mediaType?: string; /** Rating on the 1-10 scale. */ rating?: number; mainCredit?: string; reviewText?: string; isRevisit: boolean; } | { kind: 'event-rsvp'; rsvpStatus: 'going' | 'interested' | 'notgoing' | 'unknown'; eventName?: string; startsAt?: string; endsAt?: string; mode?: 'inperson' | 'virtual' | 'hybrid'; locationName?: string; locationLocality?: string; locationCountry?: string; } | { kind: 'verification'; /** Keytrace claim type (`github`, `dns`, ...) or `bluesky` for a bsky verification. */ platform: string; verified: boolean; subjectLabel?: string; /** The verified handle (Bluesky verifications only). */ handle?: string; profileUrl?: string; } | { kind: 'membership'; communityName?: string; description?: string; /** The community record's at-uri (renderer builds the outbound link). */ communityUri?: string; } | { kind: 'location'; venueName?: string; shout?: string; address?: StreamAddress; geo?: StreamGeo; } | { kind: 'travel'; origin?: string; destination?: string; /** Raw transportation mode, e.g. `flight` (renderer maps to a label). */ transportation?: string; carrier?: string; carrierCode?: string; startDate?: string; endDate?: string; } | { kind: 'standard-site'; title?: string; description?: string; /** Publication base URL (renderer derives host / builds the canonical link). */ siteUrl?: string; path?: string; /** Resolved from the publisher registry when the host is allowlisted. */ publisherName?: string; /** Publication icon — a resolved CDN URL added by AppView enrichment. */ icon?: string; /** Document cover — a resolved CDN URL added by AppView enrichment. */ coverImageUrl?: string; /** Estimated reading time in minutes. */ readingTime?: number; publishedAt?: string; }; /** * A repost / reply / quote target. Three shapes: a full post (normalized * through the same transform), a person (DID → hydrated client-side), or a * bare referenced record (e.g. the question an answer replies to). */ type StreamCardSubject = { kind: 'post'; post: StreamCardVM; } | { kind: 'person'; did: string; handle?: string; displayName?: string; avatar?: string; } | { kind: 'record'; uri: string; title?: string; }; /** * Presentation-ready, framework-free, serializable view-model for one activity * item. Produced by {@link toStreamCardVM}. Common chrome fields are always * present; `body` is a discriminated union both renderers switch on. */ interface StreamCardVM { /** at-uri: stable key + permalink source. */ uri: string; cid: string; verb: StreamVerb; source: StreamSource; /** Original author identity, chiefly for nested subject (quote/repost) cards. */ author?: StreamAuthor; tier: ActivityTier; /** ISO 8601. Record `createdAt` when present, else the AppView index time. */ timestamp: string; /** Human sentence fragment, verb-aware. */ title: string; /** * Canonical http(s) permalink to this record on its source app (e.g. a * Bluesky post's `bsky.app` URL). Absent when no linkable URL is available — * see {@link resolveCardUrl}, which keys some apps on the author handle the * transform may not have. Consumers use this to make the card click through. */ sourceUrl?: string; body?: StreamCardBody; media?: StreamMedia[]; externalLink?: StreamExternalLink; theme?: StreamTheme; subject?: StreamCardSubject; /** * Contributor and attachment detail for a hypercert claim, passed through * from the `ActivityItem` sifa-api hydrated. Renderers show a credit roll * and the claim's outbound links instead of a bare contributor count. */ hypercertDetails?: HypercertDetailsView; /** * Certified badge + resolved accounts, passed through from the ActivityItem * sifa-api hydrated. */ certifiedDetails?: CertifiedDetailsView; } declare const streamSourceSchema: z.ZodType; declare const streamAuthorSchema: z.ZodType; declare const streamThemeSchema: z.ZodType; declare const streamMediaSchema: z.ZodType; declare const streamExternalLinkSchema: z.ZodType; declare const streamGeoSchema: z.ZodType; declare const streamAddressSchema: z.ZodType; declare const streamRichSegmentSchema: z.ZodType; declare const streamCardBodySchema: z.ZodType; /** * A stream item's repost / reply / quote target. The `post` variant nests a * full {@link streamCardVMSchema} (deferred via `z.lazy` for recursion). */ declare const streamCardSubjectSchema: z.ZodType; /** Zod schema for a {@link StreamCardVM}. Recursive through `subject.post`. */ declare const streamCardVMSchema: z.ZodType; /** * Options for {@link toStreamCardVM}. Kept out of the record so the transform * stays pure (no I/O): the app registry is runtime data, so callers inject a * color resolver built from the registry they already fetched. */ interface ToStreamCardVMOptions { /** * Resolve an app's design-token color NAME (not a hex literal) from its * appId. Falls back to `slate` when unresolved. */ resolveSourceColor?: (appId: string) => string | undefined; } /** * Normalize one {@link ActivityItem} into a framework-free {@link StreamCardVM}. * Pure: no I/O, no React, no DOM, and no resolved CDN URLs (media carries raw * blob refs so each surface builds its own URL). Total: always returns a VM. * * Reference implementations: the generic/unknown case, `app.bsky.feed.post` * (text + images + external embed), and reposts (whose `subject` is normalized * through this same function). Collections with a typed body variant * (`github-pr`, `book`, `media-review`, `event-rsvp`, `verification`, * `membership`, `location`, `travel`, `standard-site`) are enriched from their * raw record below; `at.youandme.connection` and `fyi.asq.answer` populate a * person / record `subject`. Everything else (base collections + unknown apps) * flows through {@link applyGeneric}, which extracts text / media / link / * subject from common record shapes, degrading to an empty generic body. */ declare function toStreamCardVM(item: ActivityItem, options?: ToStreamCardVMOptions): StreamCardVM; /** * Map a list of activity items to VMs, applying {@link isVisibleActivityItem} * first so both surfaces (sifa-web, page.sifa.id) filter identically — a * filtered item produces no VM rather than a null-rendering card. */ declare function toStreamCardVMs(items: ActivityItem[], options?: ToStreamCardVMOptions): StreamCardVM[]; /** * Profile completeness scoring. * * Six binary signals, equal weight: headline filled, about filled, has at least * one position, education, skill, certification. * * Score: integer 0..6. Percent: round(score / 6 * 100) -- discrete values * {0, 17, 33, 50, 67, 83, 100}. * * Canonical source of truth for completeness scoring across Sifa clients. * sifa-api computes the underlying `ProfileCompletion` shape inline (in SQL * for admin-stats queries, in route handlers elsewhere) but defers scoring * to this module. */ declare const COMPLETENESS_MAX_SCORE = 6; interface ProfileCompletion { hasHeadline: boolean; hasAbout: boolean; positionCount: number; educationCount: number; skillCount: number; certificationCount: number; } /** Pure-TS computation of the 0..6 score. */ declare function completenessScore(c: ProfileCompletion): number; /** Completion as a rounded 0..100 integer percent (matches API serialisation). */ declare function completenessPercent(c: ProfileCompletion): number; /** * Profile dimensions -- the routing signal used by the Sifa homepage to choose * between the V3 "still building" and V4 "established" variants. * * Six binary signals, equal weight: avatar, headline, about, current position, * minimum-skill threshold, and at least one education entry. The routing * threshold itself (e.g. ">= 4 of 6 -> ESTABLISHED") lives in the consumer * because different surfaces may want to cut at different bars over time. * * Canonical source of truth for dimension scoring across Sifa clients. * sifa-api computes the underlying `ProfileDimensionInputs` shape inline * (in SQL on the session route) but defers scoring to this module so the * frontend and backend cannot drift on what "filled" means. * * See `docs/plans/2026-04-27-homepage-variants.md` (sifa-web) for the * history of why these six were chosen over the original eight. */ declare const DIMENSIONS_MAX_SCORE = 6; declare const MIN_SKILLS = 3; type DimensionKey = 'avatar' | 'headline' | 'about' | 'currentPosition' | 'skills' | 'education'; type DimensionMap = Record; /** * Minimal raw inputs needed to compute the dimension map. sifa-api can * populate this from a single composite SQL query without loading the full * Profile shape, which is the whole point of putting this in the SDK. */ interface ProfileDimensionInputs { hasAvatar: boolean; hasHeadline: boolean; hasAbout: boolean; currentPositionCount: number; skillCount: number; educationCount: number; } /** * Pure-TS computation of the 6-key boolean map from minimal inputs. * Use this when you already have aggregate counts (e.g. server-side). */ declare function dimensionsFromInputs(inputs: ProfileDimensionInputs): DimensionMap; /** * Derives `ProfileDimensionInputs` from a full Profile. Use this on the * client when you already have the profile loaded. */ declare function profileToDimensionInputs(profile: Profile): ProfileDimensionInputs; /** Convenience: full Profile -> dimension boolean map. */ declare function getFilledDimensionsMap(profile: Profile): DimensionMap; /** Count of filled dimensions, 0..6. Accepts either raw inputs or a full Profile. */ declare function countFilledDimensions(input: ProfileDimensionInputs | Profile): number; /** * Canonical rule for picking the "primary" position to surface on profile cards, * social previews, and structured-data outputs. * * Without this shared helper, different surfaces (visible hero, OG image, * , JSON-LD) re-derived "primary" inconsistently and diverged * for users with multiple concurrent roles. * * A `hidden` position is one the user chose not to show on their public profile. * It is never surfaced as the featured role — hidden wins over `primary`, so a * role flagged both hidden and primary is excluded rather than featured. * * Rules (in order): * 1. A position the user explicitly flagged `primary` AND that is still active AND not hidden. * 2. Otherwise, the active, non-hidden position with the most recent `startedAt`. * 3. Otherwise, undefined (no eligible position). */ interface PrimaryPositionCandidate { startedAt?: string; endedAt?: string; primary?: boolean; hidden?: boolean; } declare function pickPrimaryPosition(positions: readonly T[] | undefined): T | undefined; /** True when the whole employer string is a self-employment / pseudo employer. */ declare function isPseudoEmployer(company: string): boolean; /** * Lightweight "does this typeahead query look like a domain?" predicate, used to * decide whether to fire the domain grow-on-demand path (resolve-domain) on a * local typeahead miss. It is a heuristic, NOT a validator: the API re-validates * against the public suffix list. It only needs to distinguish "randstad.com" * (a domain) from "Randstad" / "Acme Corp" (a name) so a plain company name * never triggers a domain lookup. * * Accepts an optional scheme and path (`https://www.stripe.com/pricing`), a * leading `www.`, and multi-label hosts; requires at least one dot and a >=2 * alpha final label (TLD-shaped); rejects whitespace and `@` (emails). * * Runs on uncontrolled typeahead input, so it is deliberately ReDoS-safe: the * input length is bounded up front (a domain is <=253 chars), the host is * extracted with plain string ops (no scheme/path/trailing-dot regex), and each * label is validated by a single anchored, bounded pattern. There is no * ambiguous or nested quantifier anywhere, so matching is linear in the input. */ declare function looksLikeDomain(query: string): boolean; /** * Disambiguation-display helpers for the org typeahead (#159, Flow 1A-2). Two * same-name orgs must be told apart at a glance, so each row shows its domain, * country, and parent where known. */ interface DisambiguationFields { domain?: string | null; country?: string | null; parentName?: string | null; } /** * Build the muted secondary line for a typeahead row, e.g. * `spryker.com · DE · part of Spryker Holding`. Empty when nothing to show. */ declare function entityDisambiguationLabel(fields: DisambiguationFields): string; /** Convenience over {@link entityDisambiguationLabel} for a search result row. */ declare function searchResultDisambiguation(result: EntitySearchResult): string; /** Stable per-row identity for React keys and dedupe (entity id or PDL id). */ declare function entityResultKey(result: EntitySearchResult): string; /** * Anchor-quality classification for a position's `entityRef` pointer (#159). * * A durable company link tiers by the quality of its anchor, which drives the * link indicator in the UI (registry-backed reads stronger than a Sifa-scoped * PDL pointer, per the ratified D5 chip policy). The classification is derived * purely from the ref URI's host, so it is a shared, cross-platform predicate * (web today, the native app later). * * registry -- a portable external id any atproto app can resolve * (Wikidata / ROR / GLEIF). Renders as a full "Linked" indicator. * sifa -- a Sifa-scoped `sifa.id/company/` pointer for a * PDL-only company. Renders as a muted "Linked" (no glyph). * unlinked -- no ref, a malformed URL, or an unrecognized host. Free text. * * NEVER surfaced as "verified": a link is not a verification. */ declare const ENTITY_REF_ANCHORS: readonly ["registry", "sifa", "unlinked"]; type EntityRefAnchor = (typeof ENTITY_REF_ANCHORS)[number]; /** * Classify an entityRef by anchor quality. Uses strict host parsing (never * substring matching) so a hostile URL that merely contains a registry host in a * query string is treated as unlinked, not falsely linked. */ declare function classifyEntityRef(entityRef: string | null | undefined): EntityRefAnchor; /** Whether a position carries a durable link of any anchor quality. */ declare function isLinked(entityRef: string | null | undefined): boolean; /** * Approximate "is this handle a custom registrable domain?" check for the org * rendering floor. * * IMPORTANT -- this is deliberately NARROW, not authoritative. The SDK has no * public-suffix-list (PSL) dependency, so it cannot compute the true eTLD+1 * apex: it cannot tell `acme.co.uk` (an apex) from `careers.acme.co.uk` (a * subdomain that routes to the subsidiary case), nor reject every PSL-private * host. It performs only the rejects it CAN make correctly with no PSL: * - DIDs (never a domain handle), * - bare / no-dot handles and non-TLD-shaped hosts (via `looksLikeDomain`), * - known shared PDS / handle hosts (`*.bsky.social` and friends). * * The AUTHORITATIVE apex + eTLD+1 + confusable check is server-side in * sifa-api's `bind-domain-guard` (`isBindableHandleDomain`, backed by `tldts`); * `computeOrgFloorVerdict` there is the source of truth and is surfaced to * clients on the profile resolve (read it via `useOrgProfile`). Recompute here * only when a client-side floor gate is needed without a round-trip. * * TODO(#160): promote a shared PSL-backed eTLD+1 helper into the SDK (or vendor * a compact public-suffix table) and tighten this to a true apex check. * * Pure: no network, no I/O. */ declare function isRegistrableDomainHandle(handle: string): boolean; /** * The org rendering floor (#160) -- the "any AppView can evaluate" promise made * executable. An account renders as an organization when BOTH: * 1. It has an `id.sifa.org.profile` record (record present), AND * 2. Its handle is a custom registrable domain * ({@link isRegistrableDomainHandle}). * * Only the PRESENCE of the record matters here (declaration marks the account * as presenting-as-org; trust is layered separately). The handle check is LIVE: * evaluate it on every render so a lost domain drops org rendering * automatically. * * Pure: no network, no I/O. Pass the fetched record (or null) and the current * handle. */ declare function qualifiesAsOrg(record: OrgProfileRecord | null | undefined, handle: string): boolean; /** * The person-facet fields that decide whether an account has a CV worth * keeping. A structural subset of `Profile`, so callers can pass a profile * straight in. Every list is optional; `positions`, `education`, and `skills` * are always present on a real profile but typed optional here so partial * fixtures and trimmed payloads work. */ interface PersonalFacetContent { headline?: string | null; about?: string | null; positions?: unknown[]; education?: unknown[]; skills?: unknown[]; certifications?: unknown[]; projects?: unknown[]; publications?: unknown[]; volunteering?: unknown[]; involvement?: unknown[]; honors?: unknown[]; courses?: unknown[]; } /** * Does this account have a personal profile worth keeping visible? * * Decides the claim-flow default when an account claims its domain as an * organization. An empty account claiming a domain is a plain company signup: * present solely as an org, ask nothing. An account that already carries a CV * is the sole-trader case: keeping both facets is the default, and hiding the * person facet must be a deliberate choice rather than a silent side effect of * claiming. * * True when any profile section holds at least one record, or when the headline * or about text is non-blank. Whitespace-only text does not count. * * Pure: no network, no I/O. */ declare function hasPersonalProfileContent(profile: PersonalFacetContent): boolean; /** * Should this account's personal profile render at `/p/`, given the org verdict? * * The single routing predicate behind the freelancer dual-identity case: a * claimed org that set `personalProfileVisible` renders both facets, so `/p/` * serves the person instead of redirecting to `/c/`. Every other account keeps * the exclusive behaviour -- an org (claimed or merely recognized) redirects * `/p/` to `/c/`, and a plain person renders at `/p/` as always. * * Pure: no network, no I/O. */ declare function rendersPersonalProfile(org: { isOrg: boolean; recognized: boolean; personalProfileVisible?: boolean; } | null | undefined, renderPreference?: 'person' | 'company' | null): boolean; /** * Should this account's company page render at `/c/`? * * The mirror of {@link rendersPersonalProfile}. True for any company account -- * claimed or merely recognized -- EXCEPT when the owner explicitly chose to * present as a person, which suppresses the company page rather than deleting * anything. The org record stays in the PDS and the choice is reversible. * * Pure: no network, no I/O. */ declare function rendersCompanyProfile(org: { isOrg: boolean; recognized: boolean; } | null | undefined, renderPreference?: 'person' | 'company' | null): boolean; /** * Resolve the three-way "are you a person, a company, or both?" answer that the * account settings switch presents, from the two places the answer is stored. * * Precedence, highest first: * 1. `renderPreference === 'person'` -- the owner's explicit local override. * Wins even over a claimed org record, which stays inert in the PDS. * 2. A claimed org that declared `personalProfileVisible` -- `'both'`, the * sole trader. Read from the PDS record, so it is portable. * 3. Any other company account (claimed, recognized, or `renderPreference` * `'company'`) -- `'company'`. * 4. Everything else -- `'person'`. * * Pure: no network, no I/O. */ declare function resolveAccountFacetMode(profile: { org?: { isOrg: boolean; recognized: boolean; personalProfileVisible?: boolean; } | null; renderPreference?: 'person' | 'company' | null; }): AccountFacetMode; /** * Firmographic subset of a company profile that decides whether its `/c/` page * is worth indexing. A minimal STRUCTURAL type on purpose -- it does not import * sifa-web's `CompanyProfile`, so this predicate stays importable by any * consumer (sifa-web, sifa-app, third parties). Fields mirror the external * firmographics that lead a `/c/` page (Wikidata / ROR / GLEIF derived), NOT * Sifa-aggregated stats. * * Deliberately ABSENT: claim status and Sifa-derived employee count. Company * page indexability is NEVER gated on either (product decision, 2026-07-16) -- * a rich unclaimed page is indexable; a claimed but empty page is not. */ interface CompanyFirmographics { /** External canonical name (Wikidata label / registry name). */ canonicalName?: string | null; /** Free-text description / about. */ description?: string | null; /** Industry / sector label. */ industry?: string | null; /** URL of the company logo. */ logoUrl?: string | null; /** External headcount figure (e.g. Wikidata P1128). NOT the Sifa employee count. */ employeeCount?: number | null; /** Founding year or date. */ founded?: string | number | null; } /** * Minimum number of the optional firmographic fields * ({@link CompanyFirmographics} minus `canonicalName`) that must be present for * a `/c/` company page to be indexable. Named + exported so the threshold is * tunable in one place. */ declare const COMPANY_PAGE_MIN_FIRMOGRAPHIC_FIELDS = 2; /** * Is a `/c/` company page worth indexing? * * True iff it has real firmographic content: a non-empty `canonicalName` AND at * least {@link COMPANY_PAGE_MIN_FIRMOGRAPHIC_FIELDS} of the optional firmographic * fields ({@link CompanyFirmographics.description | description}, * `industry`, `logoUrl`, `employeeCount`, `founded`) present. * * NEVER gated on claim status or Sifa employee count -- there is no such * parameter (product decision, 2026-07-16). A rich unclaimed stub is indexable; * a name-only page (claimed or not) is not. * * Pure: no network, no I/O. Third-party importable. */ declare function isCompanyPageIndexable(company: CompanyFirmographics): boolean; /** * Minimal per-day activity count. Structurally satisfied by the SDK's * `HeatmapDay` (query subpath), but defined here so this predicate stays * importable from the main entrypoint without pulling the query layer -- same * structural-type discipline as {@link isCompanyPageIndexable}. */ interface DailyActivityCount { /** Calendar day in `YYYY-MM-DD` (UTC). Compared lexicographically. */ date: string; /** Number of activities recorded on that day. */ total: number; } /** * How many activities fall within the last `windowDays` calendar days. * * Sums {@link DailyActivityCount.total | total} for every day whose * {@link DailyActivityCount.date | date} is on or after the cutoff, where the * cutoff is `now` minus `windowDays` days (UTC calendar date). `YYYY-MM-DD` * strings sort chronologically, so the comparison is a plain string compare -- * no per-day `Date` parsing, no timezone drift. * * The day exactly `windowDays` ago is INCLUDED (inclusive lower bound). * * Pure: no network, no I/O. `now` is injectable for deterministic tests and * defaults to the current time. */ declare function countRecentActivity(days: readonly DailyActivityCount[], windowDays: number, now?: Date): number; /** * Shared, single-source-of-truth model for which profile sections render, in * what order, their nav grouping, and which items a given viewer may see. * * Every Sifa profile surface builds on this: the HTML profile page, the * Markdown / DOCX / print exports, and the standalone personal-site renderer. * Adding a section to {@link ALL_SECTIONS} makes it appear everywhere (the * exports drive presence + order from here, and {@link SECTION_LABELS} is an * exhaustive `Record`, so a new id is a compile error until * it has a label). * * The per-section sorts live in `./section-sorts.js`, built on the same SDK * date extractors the HTML section components use, so the surfaces cannot * diverge on ordering. */ /** * Side-nav groups, in display order. Each labels a contiguous run of sections * in {@link ALL_SECTIONS}. `labelKey` resolves against the consumer's `sections` * i18n namespace. */ declare const SECTION_GROUPS: readonly [{ readonly id: "overview"; readonly labelKey: "groupOverview"; }, { readonly id: "experience"; readonly labelKey: "groupExperience"; }, { readonly id: "qualifications"; readonly labelKey: "groupQualifications"; }, { readonly id: "more"; readonly labelKey: "groupMore"; }]; type SectionGroupId = (typeof SECTION_GROUPS)[number]['id']; /** * Profile body sections in render + nav order (evidence-first): identity and * activity lead, then living work (career, skills, projects, talks, * publications), then the formal record (credentials, education, courses), then * the rest. `ns` selects the consumer's i18n namespace for `labelKey`; `group` * ties the section to a {@link SECTION_GROUPS} header. */ declare const ALL_SECTIONS: readonly [{ readonly id: "about"; readonly labelKey: "about"; readonly ns: "profile"; readonly group: "overview"; }, { readonly id: "career"; readonly labelKey: "career"; readonly ns: "sections"; readonly group: "experience"; }, { readonly id: "skills"; readonly labelKey: "skills"; readonly ns: "sections"; readonly group: "experience"; }, { readonly id: "projects"; readonly labelKey: "projects"; readonly ns: "sections"; readonly group: "experience"; }, { readonly id: "presentations"; readonly labelKey: "talksAndSessions"; readonly ns: "sections"; readonly group: "experience"; }, { readonly id: "publications"; readonly labelKey: "publications"; readonly ns: "sections"; readonly group: "experience"; }, { readonly id: "credentials"; readonly labelKey: "credentials"; readonly ns: "sections"; readonly group: "qualifications"; }, { readonly id: "education"; readonly labelKey: "education"; readonly ns: "sections"; readonly group: "qualifications"; }, { readonly id: "courses"; readonly labelKey: "courses"; readonly ns: "sections"; readonly group: "qualifications"; }, { readonly id: "awards"; readonly labelKey: "awards"; readonly ns: "sections"; readonly group: "more"; }, { readonly id: "involvement"; readonly labelKey: "involvement"; readonly ns: "sections"; readonly group: "more"; }, { readonly id: "investments"; readonly labelKey: "investments"; readonly ns: "sections"; readonly group: "more"; }, { readonly id: "languages"; readonly labelKey: "languages"; readonly ns: "sections"; readonly group: "more"; }, { readonly id: "other-profiles"; readonly labelKey: "otherProfiles"; readonly ns: "sections"; readonly group: "more"; }]; /** Every profile section id, in canonical render order. */ type SectionId = (typeof ALL_SECTIONS)[number]['id']; /** Whether a section has content for a given profile (used to hide empty nav entries for visitors). */ declare function isSectionPopulated(profile: Profile, id: string): boolean; /** * The section ids a given viewer should see, in {@link ALL_SECTIONS} order. * Owners see every section; visitors only see populated ones. */ declare function getVisibleSectionIds(profile: Profile, isOwnProfile: boolean): SectionId[]; /** Drop items the owner has hidden from visitors. */ declare function filterHidden(items: T[] | undefined): T[]; /** * Items visible to a given viewer: owners see their own hidden items, visitors * do not. Shared by every export format (markdown, docx, print) and the public * `.md` route so the hidden rule lives in one place. */ declare function visibleItems(items: T[] | undefined, isOwnProfile: boolean): T[]; /** * English heading text for each section, keyed by {@link SectionId} (e.g. * `other-profiles` renders as "Links"). Exhaustive `Record`: * adding a section to {@link ALL_SECTIONS} without a label here is a compile * error, so a section can never silently render without (or with a stale) * heading. Consumers with i18n resolve their own localized labels via * `labelKey`; this is the canonical English fallback used by the exports. */ declare const SECTION_LABELS: Record; /** * Per-section item sorts, shared by every profile surface so ordering can't * diverge between the HTML page and the Markdown / DOCX / print exports and the * standalone personal-site renderer. Thin wrappers over the SDK date extractors * plus the range/language sorts in this directory. */ /** * Move a `primary && !endedAt` position to the top, matching the HTML career * section, so the current primary role always leads. */ declare function hoistPrimary(positions: ProfilePosition[]): ProfilePosition[]; declare const sortPositions: (items: ProfilePosition[]) => ProfilePosition[]; declare const sortEducation: (items: ProfileEducation[]) => ProfileEducation[]; declare const sortProjects: (items: ProfileProject[]) => ProfileProject[]; declare const sortPublications: (items: ProfilePublication[]) => ProfilePublication[]; declare const sortCertifications: (items: ProfileCertification[]) => ProfileCertification[]; declare const sortHonors: (items: ProfileHonor[]) => ProfileHonor[]; declare const sortLanguages: (items: ProfileLanguage[]) => ProfileLanguage[]; /** * Sort items by date range, floating ongoing entries (startDate present, endDate * absent) to the top. Items with no dates sink to the bottom. * * Used by sections whose lexicons have no explicit `current` flag -- ongoing is * implicit from a missing endDate. Without this wrapper, the shared * `dateRangeExtractor` reads `item.current` (always undefined for these items) * and sinks ongoing entries below completed ones. * * Sections using lexicon-aligned `startedAt`/`endedAt` fields (positions, * education) should use `lexiconDateExtractor` instead -- that extractor already * derives `current` correctly. */ declare function sortByActiveDateRange(items: T[]): T[]; /** Sort languages by proficiency (native first), then alphabetically within a rank. */ declare function sortLanguagesByProficiency(items: T[]): T[]; declare function isRoleLineRedundant(headline: string | null | undefined, roleLine: string | null | undefined): boolean; /** * Fixed display order of involvement headings. A group renders only when it has * records. Charity + mapped legacy volunteering fall under "Volunteering". */ declare const INVOLVEMENT_HEADING_ORDER: readonly ["Open Source", "Community", "Volunteering", "Civic", "Other"]; interface InvolvementGroup { heading: string; items: ProfileInvolvement[]; } /** * Group involvement records by their kind's display heading, in the fixed order * above, most-recent first within a group. Empty groups are omitted. An unknown * kind falls under "Other" (via the shared heading map). */ declare function groupInvolvementByHeading(items: ProfileInvolvement[]): InvolvementGroup[]; /** * Canonical metadata for the votable Sifa roadmap items: a one-line description * and the sifa-workspace issue(s) tracking each item. Single source of truth * shared by the sifa-web /roadmap page (labels come from i18n; description and * issue links come from here) and the sifa-api operator script that writes the * matching userinput.app discussion bodies, so the two surfaces cannot drift. * * Keys match VOTABLE_KEYS in sifa-api and the roadmap item keys in sifa-web. * Descriptions are product voice. */ /** Repository whose issues track roadmap items. */ declare const ROADMAP_ISSUES_REPO = "singi-labs/sifa-workspace"; interface RoadmapItemMeta { /** One-line description of the item, in product voice. */ description: string; /** sifa-workspace issue numbers tracking this item (may be empty). */ issues: number[]; } declare const ROADMAP_ITEM_META: Record; /** The GitHub issue URL for a sifa-workspace issue number. */ declare function roadmapIssueUrl(issue: number): string; /** * Sifa SDK -- public client library for the Sifa AppView on AT Protocol. * * Pre-1.0: the public API is unstable and may change in any minor release. * * @see https://github.com/singi-labs/sifa-sdk */ declare const SIFA_SDK_VERSION: string; export { ACTIVITY_TIERS, ACTIVITY_VERBS, ACTIVITY_VISIBILITY_RULES, ALL_SECTIONS, APP_CATEGORIES, APP_CATEGORY_IDS, APP_CATEGORY_MAP, APP_URL_PATTERNS, ARTIFACT_LINK_KIND_LABELS, ARTIFACT_LINK_KIND_OPTIONS, AccountFacetMode, type AccountVerification, type ActivityItem, type ActivityItemForUrl, type ActivityTaxonomy, type ActivityTier, type ActivityVerbMap, type AppCategoryId, type AppUrlPatterns, type ArtifactLinkKindOption, CALENDAR_EVENT_MODE_LABELS, CALENDAR_EVENT_STATUS_LABELS, CATEGORY_LABELS, CATEGORY_ORDER, COLLECTION_TO_APP, COMPANY_OPTIONAL_EMPLOYMENT_TYPES, COMPANY_PAGE_MIN_FIRMOGRAPHIC_FIELDS, COMPLETENESS_MAX_SCORE, CONTINENTS, COUNTRIES, type CardHealth, type CardHealthStrategy, type CompanyFirmographics, type ContinentCode, type CsvRow, DIMENSIONS_MAX_SCORE, type DailyActivityCount, type DimensionKey, type DimensionMap, type DisambiguationFields, type DisplayUrl, EMPLOYMENT_TYPE_GROUPS, EMPLOYMENT_TYPE_LABELS, ENTITY_REF_ANCHORS, type EmbedResult, type EmploymentTypeGroup, type EmploymentTypeOption, type EntityRefAnchor, EntitySearchResult, type FormatDisplayUrlOptions, INDUSTRY_OPTIONS, INVESTMENT_ROLE_LABELS, INVESTMENT_ROLE_OPTIONS, INVESTMENT_STAGE_LABELS, INVESTMENT_STAGE_OPTIONS, INVESTMENT_STATUS_LABELS, INVESTMENT_STATUS_OPTIONS, INVOLVEMENT_HEADING_ORDER, INVOLVEMENT_KIND_HEADINGS, INVOLVEMENT_KIND_LABELS, INVOLVEMENT_KIND_OPTIONS, type IndustryOption, type InvestmentOption, type InvolvementGroup, type InvolvementKindOption, type KnownAppId, type LexiconEntry, type LocationSegment, type LocationSegmentKind, type LocationSegmentsOptions, LocationValue, MIN_SKILLS, type MergedProfileSkill, ON_BEHALF_OF_EMPLOYMENT_TYPES, OPEN_TO_OPTIONS, OPEN_TO_TOKENS, OPEN_TO_TOKEN_TO_VALUE, OPEN_TO_VALUE_TO_TOKEN, type OpenToGroup, type OpenToOption, OrgProfileRecord, PLATFORM_LABELS, PLATFORM_OPTIONS, PRESENTATION_LINK_TYPE_LABELS, PRESENTATION_LINK_TYPE_OPTIONS, PRESENTATION_ROLE_LABELS, PRESENTATION_ROLE_OPTIONS, PUBLICATION_TYPE_LABELS, PUBLICATION_TYPE_OPTIONS, PUBLISHERS, type ParsedDelivery, type ParsedPresentation, type PdsProvider, PdsProviderInfo, type PersonalFacetContent, type PlatformId, type PresentationDeliverySummary, PresentationDuration, type PresentationLinkTypeOption, type PresentationRoleOption, type PrimaryPositionCandidate, Profile, ProfileCertification, type ProfileCompletion, type ProfileDimensionInputs, ProfileEducation, ProfileHonor, ProfileInvolvement, ProfileLanguage, ProfilePosition, ProfilePresentationDelivery, ProfilePresentationDeliveryRecord, ProfilePresentationRecord, ProfileProject, ProfilePublication, ProfileSkill, type PublicationTypeOption, type Publisher, ROADMAP_ISSUES_REPO, ROADMAP_ITEM_META, type RgbColor, type RoadmapItemMeta, SECTION_GROUPS, SECTION_LABELS, SIFA_SDK_VERSION, SKILL_CATEGORIES, STANDARD_PUBLISHER_ID, STREAM_VERBS, type SectionGroupId, type SectionId, type SkillCategory, type StreamAddress, type StreamAuthor, type StreamCardBody, type StreamCardSubject, type StreamCardVM, type StreamExternalLink, type StreamGeo, type StreamMedia, type StreamMediaBase, type StreamMediaBlob, type StreamMediaResolved, type StreamRichSegment, type StreamSource, type StreamTheme, type StreamVerb, type TierMeta, type ToStreamCardVMOptions, type UrlPathPolicy, VERIFICATION_PROVIDERS, type VerificationProvider, type VerificationProviderId, type VerificationSource, WORKPLACE_TYPE_LABELS, WORKPLACE_TYPE_OPTIONS, type WorkplaceTypeOption, buildMetaDescription, buildTalkSlug, categoryForApp, certDateExtractor, classifyEntityRef, completenessPercent, completenessScore, contrastRatio, countFilledDimensions, countRecentActivity, countryCodeToFlag, dateRangeExtractor, dedupeSkills, detectPdsProvider, dimensionsFromInputs, durationFromMinutes, entityDisambiguationLabel, entityResultKey, filterHidden, findIndustry, formatCompanyName, formatDateRange, formatDisplayUrl, formatDistanceToNow, formatLocation, formatPresentationDuration, formatRelativeTime, formatTimelineDate, getActivityTaxonomyVersion, getActivityTier, getActivityVerbsVersion, getAppCategoryIcon, getAppIdForCollection, getArtifactLinkKindLabel, getCalendarEventModeLabel, getCalendarEventStatusLabel, getContinent, getDisplayLabel, getEmploymentTypeLabel, getFaviconUrl, getFilledDimensionsMap, getHandleStem, getIndustryLabelKey, getInvestmentRoleLabel, getInvestmentStageLabel, getInvestmentStatusLabel, getInvolvementKindHeading, getInvolvementKindLabel, getLexiconEntry, getOpenToLabelKey, getPdsDisplayName, getPlatformLabel, getPresentationLinkTypeLabel, getPresentationRoleLabel, getPublicationTypeLabel, getPublisherByHost, getPublisherById, getPublisherFromSiteUrl, getTierMeta, getVerificationProvider, getVisibleSectionIds, getWorkplaceTypeLabel, groupInvolvementByHeading, groupSkillsByCategory, groupSkillsBySubCategory, groupSkillsForDisplay, hasPersonalProfileContent, hoistPrimary, isAppCategory, isCompanyPageIndexable, isCompanyRequired, isKnownAppId, isKnownPlatform, isKnownVerificationProvider, isLinked, isOnBehalfOfApplicable, isPseudoEmployer, isRegistrableDomainHandle, isRoleLineRedundant, isSectionPopulated, isValidRgbColor, isVisibleActivityItem, lexiconDateExtractor, limitCombiningMarks, locationSegments, looksLikeDomain, meetsContrastAA, normalizeCompanyKey, normalizeDoi, normalizeLegalForm, normalizeOpenTo, normalizePlatformId, normalizePresentationMode, normalizePresentationRole, normalizeSkillCategory, normalizeWorkplaceTypes, openToTokenToValue, openToValueToToken, parseIntendedAudiences, parseLocationString, parsePresentationDuration, parseTalkRkey, pdsProviderFromApi, pickPrimaryPosition, presentationCsvRowToRecord, presentationDeliveryCsvRowToRecord, primaryVerification, profileToDimensionInputs, qualifiesAsOrg, relativeLuminance, rendersCompanyProfile, rendersPersonalProfile, resolveAccountFacetMode, resolveCardHealth, resolveCardUrl, resolveEmbed, resolveVerifierProvider, rgbToString, roadmapIssueUrl, sanitizeDisplayText, sanitizeHandleInput, searchResultDisambiguation, singleDateExtractor, slugifyTitle, sortByActiveDateRange, sortByDateDesc, sortCertifications, sortEducation, sortHonors, sortLanguages, sortLanguagesByProficiency, sortPositions, sortProjects, sortPublications, streamAddressSchema, streamAuthorSchema, streamCardBodySchema, streamCardSubjectSchema, streamCardVMSchema, streamExternalLinkSchema, streamGeoSchema, streamMediaSchema, streamRichSegmentSchema, streamSourceSchema, streamThemeSchema, streamVerbSchema, stripHtmlToText, summarizePresentationDeliveries, toStreamCardVM, toStreamCardVMs, truncateGraphemes, verbForCollection, visibleItems, youtubeThumbnailUrl, youtubeVideoId };