Builds and returns list-API endpoint URLs for chat entity-card fetches, mapping canonical `contentRefType` strings to their type-specific query shapes. Returns `null` for types without a list endpoint, preventing spurious fetches. ## Key Components ### `canonicalContentRefType(contentRefType)` Resolves legacy `ContentRef` type aliases to their canonical `documentType`. Exported so consumers share this alias map rather than redeclaring it (e.g., `blog_post_existing` → `blog_post`). ### `buildListUrl(contentRefType, ids, base?)` Main export. Constructs a type-specific list-API URL for the given IDs, or returns `null` if the type has no list endpoint. The optional `base` prefix supports reverse-proxy deployments (default: `''` → hub-relative `/api/...`; `'/content'` → `/content/api/...`). ### `BUILDERS` (internal) Per-type URL factory map keyed by canonical `documentType`. Covers 13 fetch-mode types with their exact parameter shapes (`task_ids=` for ClickUp-backed types, `pageSize=` for blog, `limit=&filter=all` for programs, etc.). Absent keys return `null` by design — non-list types (`github_*`, `slack_message`, financials) are intentionally excluded. ### `ALIASES` (internal) Maps legacy `ContentRef` rail-vocab types to canonical `documentType` values before BUILDERS lookup. ## Usage Example ```typescript import { buildListUrl, canonicalContentRefType } from '@flamingo-stack/openframe-frontend-core/utils' // Hub-relative (default) buildListUrl('roadmap_item', ['abc', 'def']) // → '/api/roadmap?task_ids=abc,def' // With reverse-proxy prefix buildListUrl('blog_post_existing', ['x', 'y'], '/content') // → '/content/api/blog/posts?ids=x,y&pageSize=2' (alias resolved) // Type with no list endpoint → null (fetch skipped) buildListUrl('github_pr', ['z']) // → null // Wire into embedder endpoints once: endpoints.buildListUrl = (type, ids) => buildListUrl(type, ids, '/content') // Resolve alias for external comparison canonicalContentRefType('blog_post_existing') // → 'blog_post' canonicalContentRefType('webinar') // → 'webinar' ``` > **Security note:** `marketing_campaign` is handled as a literal static branch (not a `BUILDERS` entry) so CodeQL can prove no user-controlled dynamic dispatch reaches `/api/admin`. Prototype-poisoning keys (`__proto__`, `constructor`) are blocked via an explicit `hasOwnProperty` guard before dispatching into `BUILDERS`.