@singi-labs/sifa-sdk 0.19.47 → 0.19.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cards/app-url-patterns.js +20 -1
- package/dist/cards/app-url-patterns.js.map +1 -1
- package/dist/cards/visibility.js +9 -1
- package/dist/cards/visibility.js.map +1 -1
- package/dist/index.cjs +62 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/logic/following-feed-fallback.js +11 -0
- package/dist/logic/following-feed-fallback.js.map +1 -0
- package/dist/logic/index.js +2 -0
- package/dist/logic/index.js.map +1 -1
- package/dist/taxonomy/activity-tiers.js +15 -0
- package/dist/taxonomy/activity-tiers.js.map +1 -1
- package/dist/taxonomy/app-category-map.js +8 -1
- package/dist/taxonomy/app-category-map.js.map +1 -1
- package/package.json +1 -1
|
@@ -266,6 +266,22 @@ var APP_URL_PATTERNS = Object.freeze({
|
|
|
266
266
|
// no page of their own, so both link to the player. Verified live:
|
|
267
267
|
// GET /{handle} returns 200 with og:url https://rpg.actor/{handle}.
|
|
268
268
|
profileUrlPattern: "https://rpg.actor/{handle}"
|
|
269
|
+
},
|
|
270
|
+
lexidraw: {
|
|
271
|
+
// Verified live: GET /s/{did}/{rkey} returns 200 with <title> set to the
|
|
272
|
+
// scene name and an og:image built from the record's thumb blob CID. Other
|
|
273
|
+
// paths return the generic editor shell, so no profile page is assumed.
|
|
274
|
+
urlPattern: "https://lexidraw.app/s/{did}/{rkey}"
|
|
275
|
+
},
|
|
276
|
+
leadsheet: {
|
|
277
|
+
// Routes from the SPA router: /sheet/:actor/:rkey and /u/:actor.
|
|
278
|
+
urlPattern: "https://leadsheet.fm/sheet/{did}/{rkey}",
|
|
279
|
+
profileUrlPattern: "https://leadsheet.fm/u/{handle}"
|
|
280
|
+
},
|
|
281
|
+
brickster: {
|
|
282
|
+
// Verified live: GET /c/{did}/{rkey} server-renders <title>{record title}
|
|
283
|
+
// | Brickster</title>. No per-user profile route found.
|
|
284
|
+
urlPattern: "https://brickster.at/c/{did}/{rkey}"
|
|
269
285
|
}
|
|
270
286
|
});
|
|
271
287
|
var COLLECTION_TO_APP = [
|
|
@@ -360,7 +376,10 @@ var COLLECTION_TO_APP = [
|
|
|
360
376
|
["ma.tokono.byov.", "tokono"],
|
|
361
377
|
["agency.portable.", "portable"],
|
|
362
378
|
["actor.rpg.", "rpgactor"],
|
|
363
|
-
["equipment.rpg.", "rpgactor"]
|
|
379
|
+
["equipment.rpg.", "rpgactor"],
|
|
380
|
+
["app.lexidraw.", "lexidraw"],
|
|
381
|
+
["fm.leadsheet.", "leadsheet"],
|
|
382
|
+
["at.brickster.", "brickster"]
|
|
364
383
|
];
|
|
365
384
|
export {
|
|
366
385
|
APP_URL_PATTERNS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cards/app-url-patterns.ts"],"sourcesContent":["/**\n * URL patterns per app, mirroring the sifa-web registry.\n *\n * These describe how to build a clickable URL for an activity card given\n * the author's handle/did and the record rkey. The patterns are templates\n * with `{handle}`, `{did}`, and `{rkey}` placeholders; each variable is\n * URI-encoded when interpolated.\n *\n * Tier semantics:\n * - `urlPattern`: per-item URL (a specific post/event/repo). Preferred.\n * - `profileUrlPattern`: per-user profile URL on the app. Fallback.\n *\n * Apps without either pattern are not clickable.\n */\nexport interface AppUrlPatterns {\n urlPattern?: string;\n profileUrlPattern?: string;\n}\n\nexport const APP_URL_PATTERNS: Readonly<Record<string, AppUrlPatterns>> = Object.freeze({\n bluesky: {\n urlPattern: 'https://bsky.app/profile/{handle}/post/{rkey}',\n profileUrlPattern: 'https://bsky.app/profile/{handle}',\n },\n tangled: {\n profileUrlPattern: 'https://tangled.sh/{handle}',\n },\n // Community-calendar events (community.lexicon.calendar.*, incl. records\n // originally authored in the decommissioned Smoke Signal). atmo.rsvp resolves\n // any such event by did+rkey, so links point there. Internal id kept as\n // `smokesignal` for continuity with existing token/color keys.\n smokesignal: {\n urlPattern: 'https://atmo.rsvp/p/{did}/e/{rkey}',\n profileUrlPattern: 'https://atmo.rsvp/p/{did}',\n },\n whitewind: {\n urlPattern: 'https://whtwnd.com/{handle}/{rkey}',\n profileUrlPattern: 'https://whtwnd.com/{handle}',\n },\n frontpage: {\n urlPattern: 'https://frontpage.fyi/post/{did}/{rkey}',\n profileUrlPattern: 'https://frontpage.fyi/profile/{did}',\n },\n linkat: {\n profileUrlPattern: 'https://linkat.blue/{handle}',\n },\n pastesphere: {\n urlPattern: 'https://pastesphere.link/user/{handle}/snippet/{rkey}',\n profileUrlPattern: 'https://pastesphere.link/user/{handle}',\n },\n kipclip: {\n profileUrlPattern: 'https://kipclip.com/{handle}',\n },\n keytrace: {\n profileUrlPattern: 'https://keytrace.dev/@{handle}',\n },\n sifa: {\n profileUrlPattern: 'https://sifa.id/p/{handle}',\n },\n popfeed: {\n urlPattern: 'https://popfeed.social/profile/{handle}',\n profileUrlPattern: 'https://popfeed.social/profile/{handle}',\n },\n streamplace: {\n profileUrlPattern: 'https://stream.place/{handle}',\n },\n semble: {\n profileUrlPattern: 'https://semble.so/profile/{handle}',\n },\n grain: {\n urlPattern: 'https://grain.social/profile/{did}/gallery/{rkey}',\n profileUrlPattern: 'https://grain.social/profile/{did}',\n },\n youandme: { profileUrlPattern: 'https://youandme.at' },\n // atmoBB per-item thread/reply URLs are built bespoke in resolve-card-url.ts\n // (raw-colon /t/{did}/{rkey}); this is only the member-profile fallback.\n atmobb: { profileUrlPattern: 'https://atmobb.app/members/{did}' },\n // anisota: upgraded with per-record URL pattern below.\n margin: { profileUrlPattern: 'https://margin.at' },\n beaconbits: { profileUrlPattern: 'https://beaconbits.app' },\n bookhive: { profileUrlPattern: 'https://bookhive.buzz/profile/{handle}' },\n colibri: { profileUrlPattern: 'https://colibri.social' },\n collectivesocial: { profileUrlPattern: 'https://app.collectivesocial.app' },\n github: {},\n asq: {\n // Questions live at /q/{did}/{rkey}. Answer cards build their own URL\n // from the parsed subject.uri (which points to the question), so the\n // pattern below is invoked only for question records.\n urlPattern: 'https://asq.fyi/q/{did}/{rkey}',\n profileUrlPattern: 'https://asq.fyi',\n },\n passports: {\n // Per-record URLs don't exist on passports.social — its SvelteKit routes\n // stop at /profile/{handle}/{passport_slug}. Profile-level fallback only.\n profileUrlPattern: 'https://passports.social/profile/{handle}',\n },\n leaflet: {\n // Per-document URLs are short and rkey-only: https://leaflet.pub/{rkey}.\n // Each publication has its own base_path subdomain (e.g. zzstoatzz.leaflet.pub)\n // but the canonical short URL renders the same document. There is no\n // per-user profile route on leaflet.pub, so the profile fallback points\n // at the marketing site.\n urlPattern: 'https://leaflet.pub/{rkey}',\n profileUrlPattern: 'https://leaflet.pub',\n },\n spark: {\n // Verified live: GET /post/{did}/{rkey} returns 200.\n urlPattern: 'https://sprk.so/post/{did}/{rkey}',\n profileUrlPattern: 'https://sprk.so/profile/{handle}',\n },\n anisota: {\n urlPattern: 'https://anisota.net/post/{did}/{rkey}',\n profileUrlPattern: 'https://anisota.net/profile/{handle}',\n },\n nooki: {\n // Per-post URLs use slug-based routing (e.g. /post/{slug}), not rkey —\n // can't be constructed from a record. Profile fallback only.\n profileUrlPattern: 'https://nooki.me/user/{handle}',\n },\n atstore: {\n // atstore.fyi has no user-profile pages (the legacy `/@{handle}` pattern\n // resolved to a 404), and no per-review deep link exists. The per-product\n // page (https://atstore.fyi/products/{slug}) is the most specific URL\n // available — resolveCardUrl builds it from record.listingMeta.slug,\n // which sifa-api enriches by resolving the review's `subject` to the\n // referenced listing.detail record.\n profileUrlPattern: 'https://atstore.fyi',\n },\n plyr: {\n // SPA — per-record URLs require JS routing. plyr.fm doesn't expose a\n // public per-handle profile page server-side either; falls back to root.\n profileUrlPattern: 'https://plyr.fm',\n },\n atfund: {\n // at.fund is auth-gated — `/give/{did}` 307s to `/` when not signed in,\n // and there's no public per-endorsement permalink. Profile fallback only,\n // pointing at the main /give listing.\n profileUrlPattern: 'https://www.at.fund/give',\n },\n crate: {\n // Crate (app.crate.social) is an authoring dashboard with no public\n // per-record viewer or per-handle profile page — records are designed to\n // render on the maker's own site. `content` cards link out via\n // record.canonicalUrl (handled in resolveCardUrl); `note` records have no\n // public URL and render non-clickable. So no app-level pattern applies.\n },\n guestbook: {\n // Guestbook (dev.baileytownsend.guestbook) is a self-hosted, cross-site\n // widget: each site owner renders their own guestbook wherever they choose,\n // so there is no canonical public per-entry viewer or per-handle profile\n // page. Entries render non-clickable. Same shape as crate `note`.\n },\n atmorsvp: {\n // atmo.rsvp events live at /p/{did}/e/{rkey}. The per-item pattern is used\n // for the event record itself; checkin records build their URL from the\n // referenced event uri in resolveCardUrl (mirrors smokesignal rsvp).\n urlPattern: 'https://atmo.rsvp/p/{did}/e/{rkey}',\n profileUrlPattern: 'https://atmo.rsvp/p/{did}',\n },\n opensocial: {\n // Open Social (opensocial.community) is community-management infrastructure\n // with no public per-membership permalink. Profile fallback only.\n profileUrlPattern: 'https://opensocial.community',\n },\n atcommons: {\n // eu.atcommons.member records are self-asserted and live in the member's\n // own repo; the commons sites (atproto.eu, atproto.nl) render a single\n // members directory with no per-member permalink. Profile fallback only.\n profileUrlPattern: 'https://atproto.eu/members',\n },\n kevara: {\n // Kevara (professional network) has no live public web surface yet — its\n // domain doesn't resolve and there's no per-record viewer. Recognized so\n // the speaker-directory card renders with the right pill, but non-clickable.\n },\n atcr: {\n // ATCR repositories live at /r/{handle}/{repository}, where the rkey of an\n // io.atcr.repo.page record is the repository name. Verified live: GET\n // /r/{handle}/{rkey} and /u/{handle} both return 200.\n urlPattern: 'https://atcr.io/r/{handle}/{rkey}',\n profileUrlPattern: 'https://atcr.io/u/{handle}',\n },\n kich: {\n // Kich recipe viewer. Verified: GET /recipes/{rkey} returns 200 (the SPA\n // renders the recipe by rkey); /recipe/{rkey} 404s. No public per-handle\n // profile page, so the profile fallback points at the app root.\n urlPattern: 'https://kich.io/recipes/{rkey}',\n profileUrlPattern: 'https://kich.io',\n },\n pinksea: {\n // PinkSea oekaki viewer. Verified live: GET /{did}/oekaki/{rkey} returns\n // 200 AND its og:image resolves to harbor.pinksea.art/{did}/{blobCid}\n // carrying the same CID as the record, so the route really is per-record\n // and not just an SPA catch-all.\n urlPattern: 'https://pinksea.art/{did}/oekaki/{rkey}',\n profileUrlPattern: 'https://pinksea.art/{did}',\n },\n hypercerts: {\n // No confirmed public per-record viewer. hyperscan.dev is a network-wide\n // search explorer (its input takes \"DID, handle, AT-URI, or PDS host\"),\n // and hypercerts.org 404s on every claim route tried. Recognized so the\n // pill renders, but cards stay non-clickable.\n },\n certified: {\n // certified.app is fully client-rendered and returns an identical\n // \"Loading\" shell for a real DID and a nonsense one, so no per-record\n // route could be verified. Non-clickable until one is confirmed.\n },\n impactindexer: {\n // Reviews are surfaced through hyperscan.dev/review, which has no\n // per-record route. Non-clickable.\n },\n recipe: {\n // recipe.exchange viewer. Verified: GET /recipes/{rkey} returns 200 (rkey\n // alone; no did needed) and /profiles/{handle} returns 200 for the author\n // page. /recipe/{rkey} 404s.\n urlPattern: 'https://recipe.exchange/recipes/{rkey}',\n profileUrlPattern: 'https://recipe.exchange/profiles/{handle}',\n },\n aetherdocs: {\n // Aether OS is a browser OS — presentations open inside it and there's no\n // public per-record viewer. The card embeds the deck from the record; the\n // outbound link points at the author's Aether OS space (mirrors aetheros).\n profileUrlPattern: 'https://aetheros.computer/{handle}',\n },\n zeens: {\n // Zeens (zeens.app) still uses its former app.photosky.* namespace. The\n // app redirects /collection/{handle}/{rkey} to the canonical path below.\n urlPattern: 'https://zeens.app/profile/{handle}/collections/{rkey}',\n profileUrlPattern: 'https://zeens.app/profile/{handle}',\n },\n chive: {\n // Chive's eprint route is a catch-all that decodes a single URI-encoded\n // at-uri segment: /eprints/<encodeURIComponent(at://did/nsid/rkey)>.\n // {did} and {rkey} are URI-encoded on interpolation, so the separators\n // are pre-encoded as literals here rather than resolved bespoke.\n urlPattern: 'https://chive.pub/eprints/at%3A%2F%2F{did}%2Fpub.chive.eprint.submission%2F{rkey}',\n profileUrlPattern: 'https://chive.pub/authors/{did}',\n },\n dropanchor: {\n // Dropanchor (dropanchor.app) renders each check-in at /checkin/{rkey} —\n // the record's own rkey, no did/handle needed. Documented as the OG-tagged\n // share URL in the dropanchorapp repos. A self-permalink for this record.\n urlPattern: 'https://dropanchor.app/checkin/{rkey}',\n },\n askeverything: {\n // Ask Everything (askeverything.app) keys its public answer route\n // (/answer/{id}) on an internal DB id, not the atproto rkey, so there's no\n // per-record permalink we can build from {did, rkey}. Its profile route\n // /profile/{handle} does resolve by atproto handle, so fall back to that.\n profileUrlPattern: 'https://askeverything.app/profile/{handle}',\n },\n greengale: {\n // GreenGale (greengale.app) renders each blog entry at /{handle}/{rkey}.\n // Verified live against a real post. Self-permalink for this record.\n urlPattern: 'https://greengale.app/{handle}/{rkey}',\n profileUrlPattern: 'https://greengale.app/{handle}',\n },\n kut: {\n // Kut (kut.social) is a community app. A membership record has no page of\n // its own; the card links to the community it joined, built from the\n // membership's community.uri in resolve-card-url. Homepage fallback only.\n profileUrlPattern: 'https://kut.social',\n },\n atmob: {\n // atmob (atmob.in) is a paste app. Its SPA viewer renders any paste by its\n // full at-uri via a `?uri=` query param (verified in the app's app.js:\n // `location.search = ?uri=${encodeURIComponent(uri)}`). Both paste.document\n // and paste.bundle share that viewer, so the per-record URL is built\n // bespoke from the item's own uri in resolve-card-url. No pattern here.\n },\n tokono: {\n // tokono (tokono.ma) hosts \"bring your own video\" records, but tokono.ma is\n // the owner's static personal site with no public per-video viewer route.\n // Cards render non-clickable.\n },\n portable: {\n // agency.portable (handle portable.agency) has no resolving website, and a\n // membership record has no page of its own. Cards render non-clickable.\n },\n rpgactor: {\n // rpg.actor renders one character page per player, holding the sprite and\n // every equipped item. Sprites (rkey self) and items (rkey = item id) have\n // no page of their own, so both link to the player. Verified live:\n // GET /{handle} returns 200 with og:url https://rpg.actor/{handle}.\n profileUrlPattern: 'https://rpg.actor/{handle}',\n },\n});\n\n/**\n * Map collection NSID prefixes to app ids. Order matters: longer / more\n * specific prefixes must come before broader ones.\n */\nexport const COLLECTION_TO_APP: ReadonlyArray<readonly [prefix: string, appId: string]> = [\n ['app.bsky.', 'bluesky'],\n ['sh.tangled.', 'tangled'],\n ['events.smokesignal.', 'smokesignal'],\n ['community.lexicon.calendar.', 'smokesignal'],\n ['community.lexicon.bookmarks.', 'kipclip'],\n ['com.kipclip.', 'kipclip'],\n ['blue.flashes.', 'flashes'],\n ['social.grain.', 'grain'],\n ['pics.pixl.', 'pixl'],\n ['com.whtwnd.', 'whitewind'],\n ['fyi.unravel.frontpage.', 'frontpage'],\n ['social.psky.', 'picosky'],\n ['blue.linkat.', 'linkat'],\n ['link.pastesphere.', 'pastesphere'],\n ['site.standard.', 'standard'],\n ['computer.aetheros.', 'aetheros'],\n ['space.roomy.', 'roomy'],\n ['dev.keytrace.', 'keytrace'],\n ['social.popfeed.', 'popfeed'],\n ['app.popsky.', 'popfeed'],\n ['place.stream.', 'streamplace'],\n ['app.sidetrail.', 'semble'],\n ['network.cosmik.', 'cosmik'],\n ['id.sifa.', 'sifa'],\n ['forum.barazo.', 'barazo'],\n ['xyz.statusphere.', 'statusphere'],\n ['at.youandme.', 'youandme'],\n ['net.anisota.', 'anisota'],\n ['at.margin.', 'margin'],\n ['app.beaconbits.', 'beaconbits'],\n ['buzz.bookhive.', 'bookhive'],\n ['social.colibri.', 'colibri'],\n ['social.passports.', 'passports'],\n ['fyi.asq.', 'asq'],\n ['pub.leaflet.', 'leaflet'],\n ['so.sprk.', 'spark'],\n ['community.nooki.', 'nooki'],\n ['fyi.atstore.', 'atstore'],\n ['fm.plyr.', 'plyr'],\n ['fund.at.', 'atfund'],\n ['social.crate.', 'crate'],\n ['quest.atmo.', 'atmorsvp'],\n ['community.opensocial.', 'opensocial'],\n ['eu.atcommons.', 'atcommons'],\n ['is.kevara.', 'kevara'],\n ['io.atcr.', 'atcr'],\n // Batch onboard — named pills for apps that were leaking as raw NSIDs.\n ['app.mcp.', 'mcp'],\n ['app.userinput.', 'userinput'],\n ['com.minomobi.', 'minomobi'],\n ['com.voxport.', 'voxport'],\n ['community.lexicon.badge.', 'badges'],\n ['dev.atvouch.', 'atvouch'],\n ['io.calabro.watsm.', 'watsm'],\n ['li.plonk.', 'plonk'],\n ['net.alternativeproto.', 'alternativeproto'],\n ['one.papili.', 'papili'],\n ['org.v-it.', 'vit'],\n ['st.lifepo.', 'lifepo'],\n ['stream.thought.', 'streamthought'],\n ['tech.waow.', 'waow'],\n ['wiki.lichen.', 'lichen'],\n // Onboard from aramzs.xyz profile audit.\n ['os.aether.', 'aetherdocs'],\n ['io.kich.recipe.recipe', 'kich'],\n // Onboard from moll.dev profile audit.\n ['exchange.recipe.recipe', 'recipe'],\n // Onboard from willow.sh profile audit.\n ['dev.baileytownsend.guestbook.', 'guestbook'],\n // Onboard from trezy.codes profile audit.\n ['app.atmobb.', 'atmobb'],\n // Onboard from aaronstevenwhite.io profile audit.\n ['pub.chive.', 'chive'],\n // Onboard from helene-cook.eu profile audit. Zeens kept its old NSID.\n ['app.photosky.', 'zeens'],\n // Onboard from the holke.xyz profile audit.\n ['org.hypercerts.', 'hypercerts'],\n ['app.certified.', 'certified'],\n ['org.impactindexer.', 'impactindexer'],\n ['com.shinolabs.pinksea.', 'pinksea'],\n // Onboard from tijs.org profile audit.\n ['app.dropanchor.', 'dropanchor'],\n // Onboard from angrydutchman.houseofmadness.org profile audit.\n ['app.askeverything.', 'askeverything'],\n ['app.greengale.', 'greengale'],\n ['social.kut.', 'kut'],\n // Onboard from the upcoming profile-of-the-day audit.\n ['in.atmob.paste.', 'atmob'],\n ['ma.tokono.byov.', 'tokono'],\n ['agency.portable.', 'portable'],\n ['actor.rpg.', 'rpgactor'],\n ['equipment.rpg.', 'rpgactor'],\n];\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/cards/app-url-patterns.ts"],"sourcesContent":["/**\n * URL patterns per app, mirroring the sifa-web registry.\n *\n * These describe how to build a clickable URL for an activity card given\n * the author's handle/did and the record rkey. The patterns are templates\n * with `{handle}`, `{did}`, and `{rkey}` placeholders; each variable is\n * URI-encoded when interpolated.\n *\n * Tier semantics:\n * - `urlPattern`: per-item URL (a specific post/event/repo). Preferred.\n * - `profileUrlPattern`: per-user profile URL on the app. Fallback.\n *\n * Apps without either pattern are not clickable.\n */\nexport interface AppUrlPatterns {\n urlPattern?: string;\n profileUrlPattern?: string;\n}\n\nexport const APP_URL_PATTERNS: Readonly<Record<string, AppUrlPatterns>> = Object.freeze({\n bluesky: {\n urlPattern: 'https://bsky.app/profile/{handle}/post/{rkey}',\n profileUrlPattern: 'https://bsky.app/profile/{handle}',\n },\n tangled: {\n profileUrlPattern: 'https://tangled.sh/{handle}',\n },\n // Community-calendar events (community.lexicon.calendar.*, incl. records\n // originally authored in the decommissioned Smoke Signal). atmo.rsvp resolves\n // any such event by did+rkey, so links point there. Internal id kept as\n // `smokesignal` for continuity with existing token/color keys.\n smokesignal: {\n urlPattern: 'https://atmo.rsvp/p/{did}/e/{rkey}',\n profileUrlPattern: 'https://atmo.rsvp/p/{did}',\n },\n whitewind: {\n urlPattern: 'https://whtwnd.com/{handle}/{rkey}',\n profileUrlPattern: 'https://whtwnd.com/{handle}',\n },\n frontpage: {\n urlPattern: 'https://frontpage.fyi/post/{did}/{rkey}',\n profileUrlPattern: 'https://frontpage.fyi/profile/{did}',\n },\n linkat: {\n profileUrlPattern: 'https://linkat.blue/{handle}',\n },\n pastesphere: {\n urlPattern: 'https://pastesphere.link/user/{handle}/snippet/{rkey}',\n profileUrlPattern: 'https://pastesphere.link/user/{handle}',\n },\n kipclip: {\n profileUrlPattern: 'https://kipclip.com/{handle}',\n },\n keytrace: {\n profileUrlPattern: 'https://keytrace.dev/@{handle}',\n },\n sifa: {\n profileUrlPattern: 'https://sifa.id/p/{handle}',\n },\n popfeed: {\n urlPattern: 'https://popfeed.social/profile/{handle}',\n profileUrlPattern: 'https://popfeed.social/profile/{handle}',\n },\n streamplace: {\n profileUrlPattern: 'https://stream.place/{handle}',\n },\n semble: {\n profileUrlPattern: 'https://semble.so/profile/{handle}',\n },\n grain: {\n urlPattern: 'https://grain.social/profile/{did}/gallery/{rkey}',\n profileUrlPattern: 'https://grain.social/profile/{did}',\n },\n youandme: { profileUrlPattern: 'https://youandme.at' },\n // atmoBB per-item thread/reply URLs are built bespoke in resolve-card-url.ts\n // (raw-colon /t/{did}/{rkey}); this is only the member-profile fallback.\n atmobb: { profileUrlPattern: 'https://atmobb.app/members/{did}' },\n // anisota: upgraded with per-record URL pattern below.\n margin: { profileUrlPattern: 'https://margin.at' },\n beaconbits: { profileUrlPattern: 'https://beaconbits.app' },\n bookhive: { profileUrlPattern: 'https://bookhive.buzz/profile/{handle}' },\n colibri: { profileUrlPattern: 'https://colibri.social' },\n collectivesocial: { profileUrlPattern: 'https://app.collectivesocial.app' },\n github: {},\n asq: {\n // Questions live at /q/{did}/{rkey}. Answer cards build their own URL\n // from the parsed subject.uri (which points to the question), so the\n // pattern below is invoked only for question records.\n urlPattern: 'https://asq.fyi/q/{did}/{rkey}',\n profileUrlPattern: 'https://asq.fyi',\n },\n passports: {\n // Per-record URLs don't exist on passports.social — its SvelteKit routes\n // stop at /profile/{handle}/{passport_slug}. Profile-level fallback only.\n profileUrlPattern: 'https://passports.social/profile/{handle}',\n },\n leaflet: {\n // Per-document URLs are short and rkey-only: https://leaflet.pub/{rkey}.\n // Each publication has its own base_path subdomain (e.g. zzstoatzz.leaflet.pub)\n // but the canonical short URL renders the same document. There is no\n // per-user profile route on leaflet.pub, so the profile fallback points\n // at the marketing site.\n urlPattern: 'https://leaflet.pub/{rkey}',\n profileUrlPattern: 'https://leaflet.pub',\n },\n spark: {\n // Verified live: GET /post/{did}/{rkey} returns 200.\n urlPattern: 'https://sprk.so/post/{did}/{rkey}',\n profileUrlPattern: 'https://sprk.so/profile/{handle}',\n },\n anisota: {\n urlPattern: 'https://anisota.net/post/{did}/{rkey}',\n profileUrlPattern: 'https://anisota.net/profile/{handle}',\n },\n nooki: {\n // Per-post URLs use slug-based routing (e.g. /post/{slug}), not rkey —\n // can't be constructed from a record. Profile fallback only.\n profileUrlPattern: 'https://nooki.me/user/{handle}',\n },\n atstore: {\n // atstore.fyi has no user-profile pages (the legacy `/@{handle}` pattern\n // resolved to a 404), and no per-review deep link exists. The per-product\n // page (https://atstore.fyi/products/{slug}) is the most specific URL\n // available — resolveCardUrl builds it from record.listingMeta.slug,\n // which sifa-api enriches by resolving the review's `subject` to the\n // referenced listing.detail record.\n profileUrlPattern: 'https://atstore.fyi',\n },\n plyr: {\n // SPA — per-record URLs require JS routing. plyr.fm doesn't expose a\n // public per-handle profile page server-side either; falls back to root.\n profileUrlPattern: 'https://plyr.fm',\n },\n atfund: {\n // at.fund is auth-gated — `/give/{did}` 307s to `/` when not signed in,\n // and there's no public per-endorsement permalink. Profile fallback only,\n // pointing at the main /give listing.\n profileUrlPattern: 'https://www.at.fund/give',\n },\n crate: {\n // Crate (app.crate.social) is an authoring dashboard with no public\n // per-record viewer or per-handle profile page — records are designed to\n // render on the maker's own site. `content` cards link out via\n // record.canonicalUrl (handled in resolveCardUrl); `note` records have no\n // public URL and render non-clickable. So no app-level pattern applies.\n },\n guestbook: {\n // Guestbook (dev.baileytownsend.guestbook) is a self-hosted, cross-site\n // widget: each site owner renders their own guestbook wherever they choose,\n // so there is no canonical public per-entry viewer or per-handle profile\n // page. Entries render non-clickable. Same shape as crate `note`.\n },\n atmorsvp: {\n // atmo.rsvp events live at /p/{did}/e/{rkey}. The per-item pattern is used\n // for the event record itself; checkin records build their URL from the\n // referenced event uri in resolveCardUrl (mirrors smokesignal rsvp).\n urlPattern: 'https://atmo.rsvp/p/{did}/e/{rkey}',\n profileUrlPattern: 'https://atmo.rsvp/p/{did}',\n },\n opensocial: {\n // Open Social (opensocial.community) is community-management infrastructure\n // with no public per-membership permalink. Profile fallback only.\n profileUrlPattern: 'https://opensocial.community',\n },\n atcommons: {\n // eu.atcommons.member records are self-asserted and live in the member's\n // own repo; the commons sites (atproto.eu, atproto.nl) render a single\n // members directory with no per-member permalink. Profile fallback only.\n profileUrlPattern: 'https://atproto.eu/members',\n },\n kevara: {\n // Kevara (professional network) has no live public web surface yet — its\n // domain doesn't resolve and there's no per-record viewer. Recognized so\n // the speaker-directory card renders with the right pill, but non-clickable.\n },\n atcr: {\n // ATCR repositories live at /r/{handle}/{repository}, where the rkey of an\n // io.atcr.repo.page record is the repository name. Verified live: GET\n // /r/{handle}/{rkey} and /u/{handle} both return 200.\n urlPattern: 'https://atcr.io/r/{handle}/{rkey}',\n profileUrlPattern: 'https://atcr.io/u/{handle}',\n },\n kich: {\n // Kich recipe viewer. Verified: GET /recipes/{rkey} returns 200 (the SPA\n // renders the recipe by rkey); /recipe/{rkey} 404s. No public per-handle\n // profile page, so the profile fallback points at the app root.\n urlPattern: 'https://kich.io/recipes/{rkey}',\n profileUrlPattern: 'https://kich.io',\n },\n pinksea: {\n // PinkSea oekaki viewer. Verified live: GET /{did}/oekaki/{rkey} returns\n // 200 AND its og:image resolves to harbor.pinksea.art/{did}/{blobCid}\n // carrying the same CID as the record, so the route really is per-record\n // and not just an SPA catch-all.\n urlPattern: 'https://pinksea.art/{did}/oekaki/{rkey}',\n profileUrlPattern: 'https://pinksea.art/{did}',\n },\n hypercerts: {\n // No confirmed public per-record viewer. hyperscan.dev is a network-wide\n // search explorer (its input takes \"DID, handle, AT-URI, or PDS host\"),\n // and hypercerts.org 404s on every claim route tried. Recognized so the\n // pill renders, but cards stay non-clickable.\n },\n certified: {\n // certified.app is fully client-rendered and returns an identical\n // \"Loading\" shell for a real DID and a nonsense one, so no per-record\n // route could be verified. Non-clickable until one is confirmed.\n },\n impactindexer: {\n // Reviews are surfaced through hyperscan.dev/review, which has no\n // per-record route. Non-clickable.\n },\n recipe: {\n // recipe.exchange viewer. Verified: GET /recipes/{rkey} returns 200 (rkey\n // alone; no did needed) and /profiles/{handle} returns 200 for the author\n // page. /recipe/{rkey} 404s.\n urlPattern: 'https://recipe.exchange/recipes/{rkey}',\n profileUrlPattern: 'https://recipe.exchange/profiles/{handle}',\n },\n aetherdocs: {\n // Aether OS is a browser OS — presentations open inside it and there's no\n // public per-record viewer. The card embeds the deck from the record; the\n // outbound link points at the author's Aether OS space (mirrors aetheros).\n profileUrlPattern: 'https://aetheros.computer/{handle}',\n },\n zeens: {\n // Zeens (zeens.app) still uses its former app.photosky.* namespace. The\n // app redirects /collection/{handle}/{rkey} to the canonical path below.\n urlPattern: 'https://zeens.app/profile/{handle}/collections/{rkey}',\n profileUrlPattern: 'https://zeens.app/profile/{handle}',\n },\n chive: {\n // Chive's eprint route is a catch-all that decodes a single URI-encoded\n // at-uri segment: /eprints/<encodeURIComponent(at://did/nsid/rkey)>.\n // {did} and {rkey} are URI-encoded on interpolation, so the separators\n // are pre-encoded as literals here rather than resolved bespoke.\n urlPattern: 'https://chive.pub/eprints/at%3A%2F%2F{did}%2Fpub.chive.eprint.submission%2F{rkey}',\n profileUrlPattern: 'https://chive.pub/authors/{did}',\n },\n dropanchor: {\n // Dropanchor (dropanchor.app) renders each check-in at /checkin/{rkey} —\n // the record's own rkey, no did/handle needed. Documented as the OG-tagged\n // share URL in the dropanchorapp repos. A self-permalink for this record.\n urlPattern: 'https://dropanchor.app/checkin/{rkey}',\n },\n askeverything: {\n // Ask Everything (askeverything.app) keys its public answer route\n // (/answer/{id}) on an internal DB id, not the atproto rkey, so there's no\n // per-record permalink we can build from {did, rkey}. Its profile route\n // /profile/{handle} does resolve by atproto handle, so fall back to that.\n profileUrlPattern: 'https://askeverything.app/profile/{handle}',\n },\n greengale: {\n // GreenGale (greengale.app) renders each blog entry at /{handle}/{rkey}.\n // Verified live against a real post. Self-permalink for this record.\n urlPattern: 'https://greengale.app/{handle}/{rkey}',\n profileUrlPattern: 'https://greengale.app/{handle}',\n },\n kut: {\n // Kut (kut.social) is a community app. A membership record has no page of\n // its own; the card links to the community it joined, built from the\n // membership's community.uri in resolve-card-url. Homepage fallback only.\n profileUrlPattern: 'https://kut.social',\n },\n atmob: {\n // atmob (atmob.in) is a paste app. Its SPA viewer renders any paste by its\n // full at-uri via a `?uri=` query param (verified in the app's app.js:\n // `location.search = ?uri=${encodeURIComponent(uri)}`). Both paste.document\n // and paste.bundle share that viewer, so the per-record URL is built\n // bespoke from the item's own uri in resolve-card-url. No pattern here.\n },\n tokono: {\n // tokono (tokono.ma) hosts \"bring your own video\" records, but tokono.ma is\n // the owner's static personal site with no public per-video viewer route.\n // Cards render non-clickable.\n },\n portable: {\n // agency.portable (handle portable.agency) has no resolving website, and a\n // membership record has no page of its own. Cards render non-clickable.\n },\n rpgactor: {\n // rpg.actor renders one character page per player, holding the sprite and\n // every equipped item. Sprites (rkey self) and items (rkey = item id) have\n // no page of their own, so both link to the player. Verified live:\n // GET /{handle} returns 200 with og:url https://rpg.actor/{handle}.\n profileUrlPattern: 'https://rpg.actor/{handle}',\n },\n lexidraw: {\n // Verified live: GET /s/{did}/{rkey} returns 200 with <title> set to the\n // scene name and an og:image built from the record's thumb blob CID. Other\n // paths return the generic editor shell, so no profile page is assumed.\n urlPattern: 'https://lexidraw.app/s/{did}/{rkey}',\n },\n leadsheet: {\n // Routes from the SPA router: /sheet/:actor/:rkey and /u/:actor.\n urlPattern: 'https://leadsheet.fm/sheet/{did}/{rkey}',\n profileUrlPattern: 'https://leadsheet.fm/u/{handle}',\n },\n brickster: {\n // Verified live: GET /c/{did}/{rkey} server-renders <title>{record title}\n // | Brickster</title>. No per-user profile route found.\n urlPattern: 'https://brickster.at/c/{did}/{rkey}',\n },\n});\n\n/**\n * Map collection NSID prefixes to app ids. Order matters: longer / more\n * specific prefixes must come before broader ones.\n */\nexport const COLLECTION_TO_APP: ReadonlyArray<readonly [prefix: string, appId: string]> = [\n ['app.bsky.', 'bluesky'],\n ['sh.tangled.', 'tangled'],\n ['events.smokesignal.', 'smokesignal'],\n ['community.lexicon.calendar.', 'smokesignal'],\n ['community.lexicon.bookmarks.', 'kipclip'],\n ['com.kipclip.', 'kipclip'],\n ['blue.flashes.', 'flashes'],\n ['social.grain.', 'grain'],\n ['pics.pixl.', 'pixl'],\n ['com.whtwnd.', 'whitewind'],\n ['fyi.unravel.frontpage.', 'frontpage'],\n ['social.psky.', 'picosky'],\n ['blue.linkat.', 'linkat'],\n ['link.pastesphere.', 'pastesphere'],\n ['site.standard.', 'standard'],\n ['computer.aetheros.', 'aetheros'],\n ['space.roomy.', 'roomy'],\n ['dev.keytrace.', 'keytrace'],\n ['social.popfeed.', 'popfeed'],\n ['app.popsky.', 'popfeed'],\n ['place.stream.', 'streamplace'],\n ['app.sidetrail.', 'semble'],\n ['network.cosmik.', 'cosmik'],\n ['id.sifa.', 'sifa'],\n ['forum.barazo.', 'barazo'],\n ['xyz.statusphere.', 'statusphere'],\n ['at.youandme.', 'youandme'],\n ['net.anisota.', 'anisota'],\n ['at.margin.', 'margin'],\n ['app.beaconbits.', 'beaconbits'],\n ['buzz.bookhive.', 'bookhive'],\n ['social.colibri.', 'colibri'],\n ['social.passports.', 'passports'],\n ['fyi.asq.', 'asq'],\n ['pub.leaflet.', 'leaflet'],\n ['so.sprk.', 'spark'],\n ['community.nooki.', 'nooki'],\n ['fyi.atstore.', 'atstore'],\n ['fm.plyr.', 'plyr'],\n ['fund.at.', 'atfund'],\n ['social.crate.', 'crate'],\n ['quest.atmo.', 'atmorsvp'],\n ['community.opensocial.', 'opensocial'],\n ['eu.atcommons.', 'atcommons'],\n ['is.kevara.', 'kevara'],\n ['io.atcr.', 'atcr'],\n // Batch onboard — named pills for apps that were leaking as raw NSIDs.\n ['app.mcp.', 'mcp'],\n ['app.userinput.', 'userinput'],\n ['com.minomobi.', 'minomobi'],\n ['com.voxport.', 'voxport'],\n ['community.lexicon.badge.', 'badges'],\n ['dev.atvouch.', 'atvouch'],\n ['io.calabro.watsm.', 'watsm'],\n ['li.plonk.', 'plonk'],\n ['net.alternativeproto.', 'alternativeproto'],\n ['one.papili.', 'papili'],\n ['org.v-it.', 'vit'],\n ['st.lifepo.', 'lifepo'],\n ['stream.thought.', 'streamthought'],\n ['tech.waow.', 'waow'],\n ['wiki.lichen.', 'lichen'],\n // Onboard from aramzs.xyz profile audit.\n ['os.aether.', 'aetherdocs'],\n ['io.kich.recipe.recipe', 'kich'],\n // Onboard from moll.dev profile audit.\n ['exchange.recipe.recipe', 'recipe'],\n // Onboard from willow.sh profile audit.\n ['dev.baileytownsend.guestbook.', 'guestbook'],\n // Onboard from trezy.codes profile audit.\n ['app.atmobb.', 'atmobb'],\n // Onboard from aaronstevenwhite.io profile audit.\n ['pub.chive.', 'chive'],\n // Onboard from helene-cook.eu profile audit. Zeens kept its old NSID.\n ['app.photosky.', 'zeens'],\n // Onboard from the holke.xyz profile audit.\n ['org.hypercerts.', 'hypercerts'],\n ['app.certified.', 'certified'],\n ['org.impactindexer.', 'impactindexer'],\n ['com.shinolabs.pinksea.', 'pinksea'],\n // Onboard from tijs.org profile audit.\n ['app.dropanchor.', 'dropanchor'],\n // Onboard from angrydutchman.houseofmadness.org profile audit.\n ['app.askeverything.', 'askeverything'],\n ['app.greengale.', 'greengale'],\n ['social.kut.', 'kut'],\n // Onboard from the upcoming profile-of-the-day audit.\n ['in.atmob.paste.', 'atmob'],\n ['ma.tokono.byov.', 'tokono'],\n ['agency.portable.', 'portable'],\n ['actor.rpg.', 'rpgactor'],\n ['equipment.rpg.', 'rpgactor'],\n ['app.lexidraw.', 'lexidraw'],\n ['fm.leadsheet.', 'leadsheet'],\n ['at.brickster.', 'brickster'],\n];\n"],"mappings":"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","names":[]}
|
package/dist/cards/visibility.js
CHANGED
|
@@ -22,6 +22,12 @@ function marginAnnotationHasBody(record) {
|
|
|
22
22
|
}
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
|
+
function leadsheetNotDraft(record) {
|
|
26
|
+
return record.draft !== true;
|
|
27
|
+
}
|
|
28
|
+
function bricksterNotDraft(record) {
|
|
29
|
+
return record.status !== "draft";
|
|
30
|
+
}
|
|
25
31
|
function certifiedBadgeAccepted(record) {
|
|
26
32
|
return record.response === "accepted";
|
|
27
33
|
}
|
|
@@ -30,7 +36,9 @@ var ACTIVITY_VISIBILITY_RULES = Object.freeze({
|
|
|
30
36
|
"app.beaconbits.beacon": beaconbitsHasContent,
|
|
31
37
|
"at.margin.bookmark": marginBookmarkHasSource,
|
|
32
38
|
"at.margin.annotation": marginAnnotationHasBody,
|
|
33
|
-
"app.certified.badge.response": certifiedBadgeAccepted
|
|
39
|
+
"app.certified.badge.response": certifiedBadgeAccepted,
|
|
40
|
+
"fm.leadsheet.sheet": leadsheetNotDraft,
|
|
41
|
+
"at.brickster.creation": bricksterNotDraft
|
|
34
42
|
});
|
|
35
43
|
function isVisibleActivityItem(collection, record) {
|
|
36
44
|
if (record === null || typeof record !== "object") return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cards/visibility.ts"],"sourcesContent":["/**\n * Per-collection visibility rules for activity items.\n *\n * These rules express \"this record has nothing meaningful to render as a card\"\n * — e.g. a BookHive shelf-add with no review/stars, or a BeaconBits pin with\n * no shout. They are a UX/card concern, distinct from the api-side blocklists\n * in sifa-api/atproto-app-registry that exclude whole collections (likes,\n * follows, games) or filter spammy record shapes (empty quote posts).\n *\n * Used in two places:\n * - sifa-api's activity feed pagination, so a page-of-N returns N visible\n * items rather than silently-dropped placeholders.\n * - sifa-web's activity-feed component (and as defense-in-depth in the card\n * components themselves), so the client never relies on a stale api.\n *\n * Adding a new rule = one PR to this file. Unknown collections default to\n * visible, so the rule set is purely additive — adding a rule can only ever\n * hide more, never expose what was hidden before.\n */\n\ntype VisibilityPredicate = (record: Record<string, unknown>) => boolean;\n\nfunction isNonEmptyString(value: unknown): boolean {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction bookhiveHasOpinion(record: Record<string, unknown>): boolean {\n const review = typeof record.review === 'string' ? record.review.trim() : '';\n const stars = typeof record.stars === 'number' ? record.stars : 0;\n return review.length > 0 || stars > 0;\n}\n\nfunction beaconbitsHasContent(record: Record<string, unknown>): boolean {\n return isNonEmptyString(record.shout) || record.post != null;\n}\n\nfunction marginBookmarkHasSource(record: Record<string, unknown>): boolean {\n return isNonEmptyString(record.source);\n}\n\nfunction marginAnnotationHasBody(record: Record<string, unknown>): boolean {\n const body = record.body;\n if (typeof body === 'string') return body.trim().length > 0;\n if (body != null && typeof body === 'object') {\n const value = (body as Record<string, unknown>).value;\n return typeof value === 'string' && value.trim().length > 0;\n }\n return false;\n}\n\n/**\n * A badge response is the recipient's own accept/decline of a badge someone\n * else awarded them. An accepted one is a credential the person holds and is\n * worth surfacing; a rejected one is not, and publishing someone's declined\n * badges would be a poor call. Anything other than \"accepted\" stays hidden.\n */\nfunction certifiedBadgeAccepted(record: Record<string, unknown>): boolean {\n return record.response === 'accepted';\n}\n\nexport const ACTIVITY_VISIBILITY_RULES: Readonly<Record<string, VisibilityPredicate>> =\n Object.freeze({\n 'buzz.bookhive.book': bookhiveHasOpinion,\n 'app.beaconbits.beacon': beaconbitsHasContent,\n 'at.margin.bookmark': marginBookmarkHasSource,\n 'at.margin.annotation': marginAnnotationHasBody,\n 'app.certified.badge.response': certifiedBadgeAccepted,\n });\n\n/**\n * Return true if the activity item should render a card in the timeline.\n * Records that fail the rule for their collection are dropped silently —\n * they exist in the user's PDS but carry no card-worthy content.\n *\n * Unknown collections (no registered rule) default to visible.\n */\nexport function isVisibleActivityItem(collection: string, record: unknown): boolean {\n if (record === null || typeof record !== 'object') return false;\n const rule = ACTIVITY_VISIBILITY_RULES[collection];\n if (!rule) return true;\n return rule(record as Record<string, unknown>);\n}\n"],"mappings":";AAsBA,SAAS,iBAAiB,OAAyB;AACjD,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC5D;AAEA,SAAS,mBAAmB,QAA0C;AACpE,QAAM,SAAS,OAAO,OAAO,WAAW,WAAW,OAAO,OAAO,KAAK,IAAI;AAC1E,QAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAChE,SAAO,OAAO,SAAS,KAAK,QAAQ;AACtC;AAEA,SAAS,qBAAqB,QAA0C;AACtE,SAAO,iBAAiB,OAAO,KAAK,KAAK,OAAO,QAAQ;AAC1D;AAEA,SAAS,wBAAwB,QAA0C;AACzE,SAAO,iBAAiB,OAAO,MAAM;AACvC;AAEA,SAAS,wBAAwB,QAA0C;AACzE,QAAM,OAAO,OAAO;AACpB,MAAI,OAAO,SAAS,SAAU,QAAO,KAAK,KAAK,EAAE,SAAS;AAC1D,MAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU;AAC5C,UAAM,QAAS,KAAiC;AAChD,WAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAAA,EAC5D;AACA,SAAO;AACT;
|
|
1
|
+
{"version":3,"sources":["../../src/cards/visibility.ts"],"sourcesContent":["/**\n * Per-collection visibility rules for activity items.\n *\n * These rules express \"this record has nothing meaningful to render as a card\"\n * — e.g. a BookHive shelf-add with no review/stars, or a BeaconBits pin with\n * no shout. They are a UX/card concern, distinct from the api-side blocklists\n * in sifa-api/atproto-app-registry that exclude whole collections (likes,\n * follows, games) or filter spammy record shapes (empty quote posts).\n *\n * Used in two places:\n * - sifa-api's activity feed pagination, so a page-of-N returns N visible\n * items rather than silently-dropped placeholders.\n * - sifa-web's activity-feed component (and as defense-in-depth in the card\n * components themselves), so the client never relies on a stale api.\n *\n * Adding a new rule = one PR to this file. Unknown collections default to\n * visible, so the rule set is purely additive — adding a rule can only ever\n * hide more, never expose what was hidden before.\n */\n\ntype VisibilityPredicate = (record: Record<string, unknown>) => boolean;\n\nfunction isNonEmptyString(value: unknown): boolean {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction bookhiveHasOpinion(record: Record<string, unknown>): boolean {\n const review = typeof record.review === 'string' ? record.review.trim() : '';\n const stars = typeof record.stars === 'number' ? record.stars : 0;\n return review.length > 0 || stars > 0;\n}\n\nfunction beaconbitsHasContent(record: Record<string, unknown>): boolean {\n return isNonEmptyString(record.shout) || record.post != null;\n}\n\nfunction marginBookmarkHasSource(record: Record<string, unknown>): boolean {\n return isNonEmptyString(record.source);\n}\n\nfunction marginAnnotationHasBody(record: Record<string, unknown>): boolean {\n const body = record.body;\n if (typeof body === 'string') return body.trim().length > 0;\n if (body != null && typeof body === 'object') {\n const value = (body as Record<string, unknown>).value;\n return typeof value === 'string' && value.trim().length > 0;\n }\n return false;\n}\n\n/**\n * A badge response is the recipient's own accept/decline of a badge someone\n * else awarded them. An accepted one is a credential the person holds and is\n * worth surfacing; a rejected one is not, and publishing someone's declined\n * badges would be a poor call. Anything other than \"accepted\" stays hidden.\n */\n/** Leadsheet marks unfinished sheets with `draft: true`. */\nfunction leadsheetNotDraft(record: Record<string, unknown>): boolean {\n return record.draft !== true;\n}\n\n/** Brickster marks unfinished builds with `status: \"draft\"`. */\nfunction bricksterNotDraft(record: Record<string, unknown>): boolean {\n return record.status !== 'draft';\n}\n\nfunction certifiedBadgeAccepted(record: Record<string, unknown>): boolean {\n return record.response === 'accepted';\n}\n\nexport const ACTIVITY_VISIBILITY_RULES: Readonly<Record<string, VisibilityPredicate>> =\n Object.freeze({\n 'buzz.bookhive.book': bookhiveHasOpinion,\n 'app.beaconbits.beacon': beaconbitsHasContent,\n 'at.margin.bookmark': marginBookmarkHasSource,\n 'at.margin.annotation': marginAnnotationHasBody,\n 'app.certified.badge.response': certifiedBadgeAccepted,\n 'fm.leadsheet.sheet': leadsheetNotDraft,\n 'at.brickster.creation': bricksterNotDraft,\n });\n\n/**\n * Return true if the activity item should render a card in the timeline.\n * Records that fail the rule for their collection are dropped silently —\n * they exist in the user's PDS but carry no card-worthy content.\n *\n * Unknown collections (no registered rule) default to visible.\n */\nexport function isVisibleActivityItem(collection: string, record: unknown): boolean {\n if (record === null || typeof record !== 'object') return false;\n const rule = ACTIVITY_VISIBILITY_RULES[collection];\n if (!rule) return true;\n return rule(record as Record<string, unknown>);\n}\n"],"mappings":";AAsBA,SAAS,iBAAiB,OAAyB;AACjD,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC5D;AAEA,SAAS,mBAAmB,QAA0C;AACpE,QAAM,SAAS,OAAO,OAAO,WAAW,WAAW,OAAO,OAAO,KAAK,IAAI;AAC1E,QAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAChE,SAAO,OAAO,SAAS,KAAK,QAAQ;AACtC;AAEA,SAAS,qBAAqB,QAA0C;AACtE,SAAO,iBAAiB,OAAO,KAAK,KAAK,OAAO,QAAQ;AAC1D;AAEA,SAAS,wBAAwB,QAA0C;AACzE,SAAO,iBAAiB,OAAO,MAAM;AACvC;AAEA,SAAS,wBAAwB,QAA0C;AACzE,QAAM,OAAO,OAAO;AACpB,MAAI,OAAO,SAAS,SAAU,QAAO,KAAK,KAAK,EAAE,SAAS;AAC1D,MAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU;AAC5C,UAAM,QAAS,KAAiC;AAChD,WAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAAA,EAC5D;AACA,SAAO;AACT;AASA,SAAS,kBAAkB,QAA0C;AACnE,SAAO,OAAO,UAAU;AAC1B;AAGA,SAAS,kBAAkB,QAA0C;AACnE,SAAO,OAAO,WAAW;AAC3B;AAEA,SAAS,uBAAuB,QAA0C;AACxE,SAAO,OAAO,aAAa;AAC7B;AAEO,IAAM,4BACX,OAAO,OAAO;AAAA,EACZ,sBAAsB;AAAA,EACtB,yBAAyB;AAAA,EACzB,sBAAsB;AAAA,EACtB,wBAAwB;AAAA,EACxB,gCAAgC;AAAA,EAChC,sBAAsB;AAAA,EACtB,yBAAyB;AAC3B,CAAC;AASI,SAAS,sBAAsB,YAAoB,QAA0B;AAClF,MAAI,WAAW,QAAQ,OAAO,WAAW,SAAU,QAAO;AAC1D,QAAM,OAAO,0BAA0B,UAAU;AACjD,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK,MAAiC;AAC/C;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -1382,7 +1382,14 @@ var APP_CATEGORY_MAP = {
|
|
|
1382
1382
|
// no backend scan collection yet)
|
|
1383
1383
|
linkat: "Links",
|
|
1384
1384
|
kipclip: "Links",
|
|
1385
|
-
statusphere: "Social"
|
|
1385
|
+
statusphere: "Social",
|
|
1386
|
+
// 2026-09-28 profile-of-the-day queue onboards.
|
|
1387
|
+
lexidraw: "Art",
|
|
1388
|
+
// app.lexidraw.scene — named whiteboard drawings
|
|
1389
|
+
leadsheet: "Music",
|
|
1390
|
+
// fm.leadsheet.sheet — authored chord sheets and tabs
|
|
1391
|
+
brickster: "Art"
|
|
1392
|
+
// at.brickster.creation — 3D brick builds
|
|
1386
1393
|
};
|
|
1387
1394
|
function isKnownAppId(appId) {
|
|
1388
1395
|
return Object.prototype.hasOwnProperty.call(APP_CATEGORY_MAP, appId);
|
|
@@ -2292,6 +2299,21 @@ var activity_tiers_default = {
|
|
|
2292
2299
|
tier: "filtered",
|
|
2293
2300
|
notes: "Statusphere emoji status \u2014 leisure signal.",
|
|
2294
2301
|
app: "statusphere"
|
|
2302
|
+
},
|
|
2303
|
+
"app.lexidraw.scene": {
|
|
2304
|
+
tier: "creation",
|
|
2305
|
+
app: "lexidraw",
|
|
2306
|
+
notes: "A named whiteboard drawing, stored as a gzipped scene blob with a thumbnail."
|
|
2307
|
+
},
|
|
2308
|
+
"fm.leadsheet.sheet": {
|
|
2309
|
+
tier: "creation",
|
|
2310
|
+
app: "leadsheet",
|
|
2311
|
+
notes: "An authored chord sheet or tab (ChordPro) for a song."
|
|
2312
|
+
},
|
|
2313
|
+
"at.brickster.creation": {
|
|
2314
|
+
tier: "creation",
|
|
2315
|
+
app: "brickster",
|
|
2316
|
+
notes: "A 3D brick build saved from the Brickster editor."
|
|
2295
2317
|
}
|
|
2296
2318
|
}
|
|
2297
2319
|
};
|
|
@@ -3901,6 +3923,22 @@ var APP_URL_PATTERNS = Object.freeze({
|
|
|
3901
3923
|
// no page of their own, so both link to the player. Verified live:
|
|
3902
3924
|
// GET /{handle} returns 200 with og:url https://rpg.actor/{handle}.
|
|
3903
3925
|
profileUrlPattern: "https://rpg.actor/{handle}"
|
|
3926
|
+
},
|
|
3927
|
+
lexidraw: {
|
|
3928
|
+
// Verified live: GET /s/{did}/{rkey} returns 200 with <title> set to the
|
|
3929
|
+
// scene name and an og:image built from the record's thumb blob CID. Other
|
|
3930
|
+
// paths return the generic editor shell, so no profile page is assumed.
|
|
3931
|
+
urlPattern: "https://lexidraw.app/s/{did}/{rkey}"
|
|
3932
|
+
},
|
|
3933
|
+
leadsheet: {
|
|
3934
|
+
// Routes from the SPA router: /sheet/:actor/:rkey and /u/:actor.
|
|
3935
|
+
urlPattern: "https://leadsheet.fm/sheet/{did}/{rkey}",
|
|
3936
|
+
profileUrlPattern: "https://leadsheet.fm/u/{handle}"
|
|
3937
|
+
},
|
|
3938
|
+
brickster: {
|
|
3939
|
+
// Verified live: GET /c/{did}/{rkey} server-renders <title>{record title}
|
|
3940
|
+
// | Brickster</title>. No per-user profile route found.
|
|
3941
|
+
urlPattern: "https://brickster.at/c/{did}/{rkey}"
|
|
3904
3942
|
}
|
|
3905
3943
|
});
|
|
3906
3944
|
var COLLECTION_TO_APP = [
|
|
@@ -3995,7 +4033,10 @@ var COLLECTION_TO_APP = [
|
|
|
3995
4033
|
["ma.tokono.byov.", "tokono"],
|
|
3996
4034
|
["agency.portable.", "portable"],
|
|
3997
4035
|
["actor.rpg.", "rpgactor"],
|
|
3998
|
-
["equipment.rpg.", "rpgactor"]
|
|
4036
|
+
["equipment.rpg.", "rpgactor"],
|
|
4037
|
+
["app.lexidraw.", "lexidraw"],
|
|
4038
|
+
["fm.leadsheet.", "leadsheet"],
|
|
4039
|
+
["at.brickster.", "brickster"]
|
|
3999
4040
|
];
|
|
4000
4041
|
|
|
4001
4042
|
// src/cards/resolve-card-url.ts
|
|
@@ -4214,6 +4255,12 @@ function marginAnnotationHasBody(record) {
|
|
|
4214
4255
|
}
|
|
4215
4256
|
return false;
|
|
4216
4257
|
}
|
|
4258
|
+
function leadsheetNotDraft(record) {
|
|
4259
|
+
return record.draft !== true;
|
|
4260
|
+
}
|
|
4261
|
+
function bricksterNotDraft(record) {
|
|
4262
|
+
return record.status !== "draft";
|
|
4263
|
+
}
|
|
4217
4264
|
function certifiedBadgeAccepted(record) {
|
|
4218
4265
|
return record.response === "accepted";
|
|
4219
4266
|
}
|
|
@@ -4222,7 +4269,9 @@ var ACTIVITY_VISIBILITY_RULES = Object.freeze({
|
|
|
4222
4269
|
"app.beaconbits.beacon": beaconbitsHasContent,
|
|
4223
4270
|
"at.margin.bookmark": marginBookmarkHasSource,
|
|
4224
4271
|
"at.margin.annotation": marginAnnotationHasBody,
|
|
4225
|
-
"app.certified.badge.response": certifiedBadgeAccepted
|
|
4272
|
+
"app.certified.badge.response": certifiedBadgeAccepted,
|
|
4273
|
+
"fm.leadsheet.sheet": leadsheetNotDraft,
|
|
4274
|
+
"at.brickster.creation": bricksterNotDraft
|
|
4226
4275
|
});
|
|
4227
4276
|
function isVisibleActivityItem(collection, record) {
|
|
4228
4277
|
if (record === null || typeof record !== "object") return false;
|
|
@@ -5854,6 +5903,14 @@ function countRecentActivity(days, windowDays, now = /* @__PURE__ */ new Date())
|
|
|
5854
5903
|
return days.reduce((sum, day) => day.date >= cutoff ? sum + day.total : sum, 0);
|
|
5855
5904
|
}
|
|
5856
5905
|
|
|
5906
|
+
// src/logic/following-feed-fallback.ts
|
|
5907
|
+
function followingFeedFallbackApp(feed) {
|
|
5908
|
+
if (!feed || feed.items.length > 0 || !feed.apps?.length) return null;
|
|
5909
|
+
let busiest = feed.apps[0];
|
|
5910
|
+
for (const app of feed.apps) if (app.count > busiest.count) busiest = app;
|
|
5911
|
+
return busiest.id;
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5857
5914
|
// src/logic/self-app.ts
|
|
5858
5915
|
var SELF_APP_ID = "sifa";
|
|
5859
5916
|
function excludeSelfApp(apps) {
|
|
@@ -7071,7 +7128,7 @@ function describeSifaRecord(collection, value) {
|
|
|
7071
7128
|
}
|
|
7072
7129
|
|
|
7073
7130
|
// src/index.ts
|
|
7074
|
-
var SIFA_SDK_VERSION = "0.19.
|
|
7131
|
+
var SIFA_SDK_VERSION = "0.19.49";
|
|
7075
7132
|
|
|
7076
7133
|
exports.ACTIVITY_TIERS = ACTIVITY_TIERS;
|
|
7077
7134
|
exports.ACTIVITY_VERBS = ACTIVITY_VERBS;
|
|
@@ -7230,6 +7287,7 @@ exports.excludeSelfApp = excludeSelfApp;
|
|
|
7230
7287
|
exports.externalRecordRefSchema = externalRecordRefSchema;
|
|
7231
7288
|
exports.filterHidden = filterHidden;
|
|
7232
7289
|
exports.findIndustry = findIndustry;
|
|
7290
|
+
exports.followingFeedFallbackApp = followingFeedFallbackApp;
|
|
7233
7291
|
exports.formatCompanyName = formatCompanyName;
|
|
7234
7292
|
exports.formatCredentialDateRange = formatCredentialDateRange;
|
|
7235
7293
|
exports.formatDateRange = formatDateRange;
|