{"version":3,"sources":["../../src/list/index.ts","../../src/list/list-scope.ts","../../src/list/entity-list.ts","../../src/list/dimension-filter.ts"],"sourcesContent":["/**\n * `@ai-matrx/records/list` — the list vocabulary every records surface shares (P16):\n * the five-lane list scope and the entity-list query, filters, sort, scope counts and\n * facets. Pure types + tiny helpers; moved from matrx-frontend `lib/list-scope` and\n * `lib/entity-list/types`.\n */\nexport * from \"./list-scope\";\nexport * from \"./entity-list\";\nexport { DIMENSION_FILTER_KEY, dimensionValueOf, withDimensionValue } from \"./dimension-filter\";\n","// lib/list-scope/types.ts\n//\n// THE VIEW LAW (CLAUDE.md + common-docs/systems/architecture/database/FEATURE.md §6):\n// RLS is the ceiling, never the view definition. Every list query MUST\n// declare its own scope explicitly — a bare `.select(\"*\")` relying on RLS\n// alone to \"just filter to mine\" is a defect the moment a user belongs to\n// more than one org (every user does: personal org + N companies).\n//\n// THE VOCABULARY IS FIXED AND LIVES HERE. A surface declares WHICH of these\n// it supports and supplies the predicate. It may not invent one of its own —\n// a scope the user learns on one page has to mean the same thing on every\n// other page. See lib/list-scope/FEATURE.md.\n//\n//   all      → the union of every visible composable lane (including Public/System)\n//   mine     → what did I make?\n//   team     → what did my team make?       (people I share a team with)\n//   orgs     → what does my organization have?\n//   shared   → what did someone hand me?    (explicit iam.permissions grant)\n//   industry → what does my field publish?  (see below)\n//   public   → what has a tenant published platform-wide?\n//   system   → what does the PLATFORM ITSELF ship?  (see below)\n//\n// SYSTEM (added 2026-08-26) is the platform's own corpus — builtin agents,\n// global shortcuts, platform content blocks: records AI Matrx publishes, not\n// records a tenant published. Which rows a viewer gets is the RPC's decision:\n// on /agents/all every signed-in person reads the PUBLISHED built-ins and a\n// platform admin reads the whole corpus (2026-09-27, agx_list_scoped).\n//\n// It is a SCOPE and not a separate admin page because that separation is what\n// produced the drift it repairs: /admin/agents\n// was a second, poorer list UI over the same table, and every feature added to\n// /agents/all (facets, server sort, the one action menu, doors, Orchestras)\n// silently skipped the system corpus. One list, one more question it answers.\n//\n// INDUSTRY is opt-in on BOTH ends, and is \"orgs\" with one more hop rather than\n// a new kind of thing: curators publish into an industry\n// (iam.industry_curators), and an org must ATTACH that industry\n// (iam.org_industries) before its members can read the corpus. Records attach\n// to an industry by GRANT ROW, following rag.data_store_grants.industry_id.\n//\n// A page renders one tab per scope the surface supports; switching scopes\n// changes the declared query, never silently reinterprets RLS output.\n//\n// TWO AXES, NEVER MIXED (Arman 2026-09-30, common-docs\n// /policies/access-ladder.md). The LANE (this union) says HOW\n// I can see a record. WHICH ORGANIZATION it is in is a separate axis — the\n// page's organization filter, `EntityListQuery.orgId` (URL `?org_filter=`, null = All\n// organizations) — and it narrows EVERY lane. A lane carries no organization id.\n// The ACTIVE organization is neither: it never reaches a list read.\n\n// THE ADMIN SEAT (Arman, 2026-09-26): \"No one acts as themselves in admin.\"\n// An admin page never shows mine / orgs / shared — those are PERSONAL-SEAT\n// questions (\"what did I make?\", \"what does MY team have?\").\n//\n// AN ADMIN MANAGEMENT PAGE HAS NO SCOPE LANES AT ALL: it manages the\n// platform's own records (the `system` scope, alone, with no tabs). Looking\n// into a tenant's records is tech support, and lives only on a separate\n// support route (…/support), which declares ADMIN_SUPPORT_LIST_SCOPES:\n//\n//   platform_orgs   → every organization's records   (narrowable to one org)\n//   platform_users  → every person's own records     (narrowable to one person,\n//                                                      by their personal org id)\n//   platform_all    → the whole platform corpus\n//\n// Guarded by `pnpm check:admin-no-personal-seat`.\n\nexport type ListScopeKind =\n  | \"all\"\n  | \"mine\"\n  | \"team\"\n  | \"orgs\"\n  | \"shared\"\n  | \"industry\"\n  | \"public\"\n  | \"system\"\n  | \"platform_orgs\"\n  | \"platform_users\"\n  | \"platform_all\";\n\nexport type ListScope =\n  /** ALL: Mine ∪ My team ∪ My Orgs ∪ Shared, one row per record. The default lane. */\n  | { kind: \"all\" }\n  | { kind: \"mine\" }\n  /**\n   * MY TEAM (access ladder: \"my team or department\"). Rows in an organization I\n   * belong to, made by someone who shares a live team with me THERE — me\n   * included where I am on a team. A person on no team sees NOTHING here (and\n   * the list says so) — never a copy of Mine (2026-09-28). A team is a\n   * list filter, never an access boundary: every row here is one \"orgs\" would\n   * also show. Server reach: `iam.my_team_reach(p_org_id)` (teams FEATURE.md).\n   * Narrowing to one organization is the organization filter's job (`orgId`).\n   */\n  | { kind: \"team\" }\n  /** Every organization I belong to; one organization = the organization filter. */\n  | { kind: \"orgs\" }\n  | { kind: \"shared\" }\n  /** `industryId: null` = blended across every industry my orgs have attached. */\n  | { kind: \"industry\"; industryId: string | null }\n  | { kind: \"public\" }\n  /** Platform-published records; the RPC decides what each viewer reads. */\n  | { kind: \"system\" }\n  /** ADMIN: every organization's records. `organizationId` narrows to one. */\n  | { kind: \"platform_orgs\"; organizationId: string | null }\n  /** ADMIN: every person's own records. `organizationId` = one person's personal org. */\n  | { kind: \"platform_users\"; organizationId: string | null }\n  /** ADMIN: the whole platform corpus. */\n  | { kind: \"platform_all\" };\n\n/**\n * The scopes an admin SUPPORT route declares (…/support, e.g. Mandate support\n * lookup) — never on a management page, never a personal-seat scope.\n */\nexport const ADMIN_SUPPORT_LIST_SCOPES: ListScopeKind[] = [\n  \"platform_orgs\",\n  \"platform_users\",\n  \"platform_all\",\n];\n\n/** The personal-seat scopes: legal on user pages, banned on admin pages. */\nexport const PERSONAL_SEAT_SCOPES: readonly ListScopeKind[] = [\"all\", \"mine\", \"team\", \"orgs\", \"shared\"];\n\n/** The lanes the All lane unions. */\nexport const ALL_LANE_MEMBERS: readonly ListScopeKind[] = [\n  \"mine\",\n  \"team\",\n  \"orgs\",\n  \"shared\",\n  \"industry\",\n  \"public\",\n  \"system\",\n];\n\n/** The only lanes that may contribute to a personal-seat All. */\nexport const VISIBLE_COMPOSABLE_LANES: readonly ListScopeKind[] = ALL_LANE_MEMBERS;\nconst STANDARD_PERSONAL_LANES: readonly ListScopeKind[] = [\"mine\", \"team\", \"orgs\", \"shared\"];\n\n/** Reserved transport filter consumed by the scoped-list SQL boundary. */\nexport const VISIBLE_LANES_FILTER = \"__visible_lanes\" as const;\n\n/** Canonical precedence for complete identity-backed list readers. */\nexport const EXCLUSIVE_LANE_PRECEDENCE: readonly ListScopeKind[] = [\n  \"system\",\n  \"mine\",\n  \"team\",\n  \"orgs\",\n  \"shared\",\n  \"industry\",\n  \"public\",\n];\n\nexport type ExclusiveLane = Exclude<(typeof EXCLUSIVE_LANE_PRECEDENCE)[number], \"all\">;\n\nexport interface LaneFacts {\n  mine: boolean;\n  inMyOrgs: boolean;\n  shared?: boolean;\n  isPublic: boolean;\n  team?: boolean;\n  industry?: boolean;\n  system?: boolean;\n}\n\n/** Classify one complete record exactly once using the canonical precedence. */\nexport function exclusiveLaneOf(facts: LaneFacts): ExclusiveLane | null {\n  if (facts.system) return \"system\";\n  if (facts.mine) return \"mine\";\n  if (facts.inMyOrgs && facts.team) return \"team\";\n  if (facts.inMyOrgs) return \"orgs\";\n  if (facts.shared === true) return \"shared\";\n  if (facts.industry) return \"industry\";\n  if (facts.isPublic) return \"public\";\n  // Last-resort fallback for readers that cannot expose grant facts.\n  if (facts.shared === undefined) return \"shared\";\n  return null;\n}\n\nexport function inExclusiveLane(facts: LaneFacts, lane: ListScopeKind): boolean {\n  const own = exclusiveLaneOf(facts);\n  return lane === \"all\" ? own !== null : own === lane;\n}\n\n/**\n * Add the display-visible lane vector to an RPC filter bag. The marker is\n * transport metadata, never an ordinary user filter and never an auth grant.\n * Unknown/absent vectors are intentionally left absent: callers must not\n * invent hidden lanes merely because the vocabulary contains them.\n */\nexport function listSourceFilters(query: {\n  filters?: Record<string, unknown> | null;\n  visibleScopes?: readonly ListScopeKind[];\n}): Record<string, unknown> {\n  const filters = { ...(query.filters ?? {}) };\n  delete filters[VISIBLE_LANES_FILTER];\n  if (query.visibleScopes) {\n    const values = [...new Set(query.visibleScopes)].filter(\n      (kind): kind is (typeof VISIBLE_COMPOSABLE_LANES)[number] =>\n        VISIBLE_COMPOSABLE_LANES.includes(kind as (typeof VISIBLE_COMPOSABLE_LANES)[number]) &&\n        kind !== \"all\",\n    );\n    filters[VISIBLE_LANES_FILTER] = { kind: \"select\", values };\n  }\n  return filters;\n}\n\n/** Every user list opens on All unless its page or knob says otherwise. */\nexport const DEFAULT_LIST_SCOPE: ListScope = { kind: \"all\" };\n\n/**\n * The vocabulary as a runtime value — for validating a scope string that\n * arrives from outside TypeScript (a counts RPC row, a URL parameter).\n *\n * Every such check reads THIS. A hand-listed subset at a call site is how the\n * `system` scope came back from the server with a real total and was silently\n * dropped on the way to its tab.\n */\nexport const LIST_SCOPE_KINDS: readonly ListScopeKind[] = [\n  \"all\",\n  \"mine\",\n  \"team\",\n  \"orgs\",\n  \"shared\",\n  \"industry\",\n  \"public\",\n  \"system\",\n  \"platform_orgs\",\n  \"platform_users\",\n  \"platform_all\",\n];\n\n// ── Narrowing helpers ───────────────────────────────────────────────────────\n\nexport function isMineScope(\n  scope: ListScope,\n): scope is Extract<ListScope, { kind: \"mine\" }> {\n  return scope.kind === \"mine\";\n}\n\nexport function isTeamScope(\n  scope: ListScope,\n): scope is Extract<ListScope, { kind: \"team\" }> {\n  return scope.kind === \"team\";\n}\n\n/** The lanes a type can structurally be unable to hold (All and Mine always exist). */\nexport type DeclarableLane = \"team\" | \"orgs\" | \"shared\" | \"industry\" | \"public\" | \"system\";\n\n/**\n * THE ONE DECLARED STATEMENT of which lanes a surface's type cannot hold\n * (`EntityListConfig.lanes`, `EntityScopeTabs lanes`): `{ public: false }` = this type has no\n * publish path, so Public would only ever say 0. A lane that is possible but empty for this\n * person is NOT declared: it stays and shows an honest 0. Omitted = every lane the surface's\n * `scopes` names is offered (and the shell adds My team beside My Orgs).\n *\n * The facts come from the type, never from who is looking: team needs a team concept on its\n * records, shared needs a `platform.shareable_resource_registry` row, public needs a publish path\n * (`is_link_shareable`, or a public-card path as agents and workflows have), system needs a\n * platform corpus of that type.\n */\nexport type LaneSupport = Partial<Record<DeclarableLane, false>>;\n\n/** What a surface declares about the standard lanes (`EntityListConfig.lanes`). */\nexport interface StandardLanesOptions {\n  lanes?: LaneSupport;\n}\n\n/** The lanes a surface declared it cannot hold. */\nexport function declaredAbsentLanes(lanes: LaneSupport | undefined): ListScopeKind[] {\n  return (Object.keys(lanes ?? {}) as DeclarableLane[]).filter((k) => lanes?.[k] === false);\n}\n\n/**\n * THE ONE PLACE \"My team\" joins a list (T-29, 2026-09-27). Every surface that\n * offers \"My Orgs\" offers \"My team\" beside it — never declared per page. Its\n * `*_list_scoped` RPC answers `p_scope = 'team'` through `iam.my_team_reach`,\n * and its counts RPC returns a `team` row; a surface whose RPC cannot is a\n * defect in that RPC, not a reason to hide the tab here.\n *\n * Inserted directly after \"mine\" (the ladder reads narrow → wide: only me, my\n * team, my organization). Idempotent; a list without \"orgs\" is untouched —\n * an admin page, or a Private type, has no team question to answer.\n */\nexport function withTeamScope(\n  scopes: readonly ListScopeKind[],\n  lanes: StandardLanesOptions = {},\n): ListScopeKind[] {\n  // A surface DECLARES the lanes its type cannot hold (`lanes: { public: false }`): a lane that\n  // can never hold anything is absent, never an empty tab (a screen never lies).\n  const absent = declaredAbsentLanes(lanes.lanes);\n  const declared = scopes.filter((k) => !absent.includes(k));\n  if (absent.includes(\"team\")) return declared;\n  if (!declared.includes(\"orgs\") || declared.includes(\"team\")) return [...declared];\n  const out = [...declared];\n  const at = out.indexOf(\"mine\");\n  out.splice(at >= 0 ? at + 1 : out.indexOf(\"orgs\"), 0, \"team\");\n  return out;\n}\n\n/**\n * THE ONE PLACE \"All\" joins a list (2026-09-30). A tab bar offering two or more\n * of the personal lanes (Mine / My team / My Orgs / Shared) gets All as its\n * FIRST tab — never declared per page. A list with one personal lane (a Private\n * type: Mine alone) or none (an admin page) is untouched: All would repeat it.\n * Idempotent; its `*_list_scoped` RPC answers `p_scope = 'all'`.\n */\nexport function withAllScope(scopes: readonly ListScopeKind[]): ListScopeKind[] {\n  if (scopes.includes(\"all\")) return [...scopes];\n  const personal = scopes.filter((k) => STANDARD_PERSONAL_LANES.includes(k)).length;\n  return personal >= 2 ? [\"all\", ...scopes] : [...scopes];\n}\n\n/** The lanes a tab bar renders: the surface's own, plus All and My team where they belong. */\nexport function withStandardLanes(\n  scopes: readonly ListScopeKind[],\n  lanes: StandardLanesOptions = {},\n): ListScopeKind[] {\n  return withAllScope(withTeamScope(scopes, lanes));\n}\n\nexport function isAllScope(\n  scope: ListScope,\n): scope is Extract<ListScope, { kind: \"all\" }> {\n  return scope.kind === \"all\";\n}\n\nexport function isOrgsScope(\n  scope: ListScope,\n): scope is Extract<ListScope, { kind: \"orgs\" }> {\n  return scope.kind === \"orgs\";\n}\n\nexport function isSharedScope(\n  scope: ListScope,\n): scope is Extract<ListScope, { kind: \"shared\" }> {\n  return scope.kind === \"shared\";\n}\n\nexport function isIndustryScope(\n  scope: ListScope,\n): scope is Extract<ListScope, { kind: \"industry\" }> {\n  return scope.kind === \"industry\";\n}\n\nexport function isSystemScope(\n  scope: ListScope,\n): scope is Extract<ListScope, { kind: \"system\" }> {\n  return scope.kind === \"system\";\n}\n\nexport function isPublicScope(\n  scope: ListScope,\n): scope is Extract<ListScope, { kind: \"public\" }> {\n  return scope.kind === \"public\";\n}\n\n/** The industry this scope narrows to, or null. */\nexport function scopeIndustryId(scope: ListScope): string | null {\n  return scope.kind === \"industry\" ? scope.industryId : null;\n}\n\n/**\n * The id a LANE itself is narrowed to — an industry, or an admin support lane's\n * one organization / person. Personal lanes never carry one: which organization\n * a list shows is the organization filter (`EntityListQuery.orgId`).\n */\nexport function scopeNarrowId(scope: ListScope): string | null {\n  if (scope.kind === \"platform_orgs\" || scope.kind === \"platform_users\")\n    return scope.organizationId;\n  if (scope.kind === \"industry\") return scope.industryId;\n  return null;\n}\n\n/**\n * THE `p_org_id` EVERY `*_list_scoped` / `*_scope_counts` / facets RPC receives:\n * the page's organization filter (null = All organizations → undefined), or —\n * on an admin support lane — that lane's own one-organization narrowing. Never\n * the active organization.\n */\nexport function listOrgParam(query: {\n  scope: ListScope;\n  orgId: string | null;\n}): string | undefined {\n  if (query.orgId) return query.orgId;\n  if (query.scope.kind === \"platform_orgs\" || query.scope.kind === \"platform_users\")\n    return query.scope.organizationId ?? undefined;\n  return undefined;\n}\n\n/** Stable identity for tab selection / React keys. */\nexport function scopeKey(scope: ListScope): string {\n  if (scope.kind === \"industry\")\n    return scope.industryId ? `industry:${scope.industryId}` : \"industry\";\n  if (scope.kind === \"platform_orgs\" || scope.kind === \"platform_users\")\n    return scope.organizationId ? `${scope.kind}:${scope.organizationId}` : scope.kind;\n  return scope.kind;\n}\n\n/** Build a scope from a kind + optional narrowing id (tab click handlers). */\nexport function makeScope(\n  kind: ListScopeKind,\n  narrowToId: string | null = null,\n): ListScope {\n  switch (kind) {\n    case \"all\":\n      return { kind: \"all\" };\n    case \"orgs\":\n      return { kind: \"orgs\" };\n    case \"team\":\n      return { kind: \"team\" };\n    case \"industry\":\n      return { kind: \"industry\", industryId: narrowToId };\n    case \"mine\":\n      return { kind: \"mine\" };\n    case \"shared\":\n      return { kind: \"shared\" };\n    case \"public\":\n      return { kind: \"public\" };\n    case \"system\":\n      return { kind: \"system\" };\n    case \"platform_orgs\":\n      return { kind: \"platform_orgs\", organizationId: narrowToId };\n    case \"platform_users\":\n      return { kind: \"platform_users\", organizationId: narrowToId };\n    case \"platform_all\":\n      return { kind: \"platform_all\" };\n    default: {\n      const _exhaustive: never = kind;\n      throw new Error(\n        `[list-scope] unknown scope kind: ${String(_exhaustive)}`,\n      );\n    }\n  }\n}\n","// lib/entity-list/types.ts\n//\n// The feature-agnostic half of a canonical entity-list surface. Extracted from\n// features/agents/browse (live at /agents/all) — the proving ground. Nothing\n// here knows what an agent is.\n//\n// THE SPLIT THAT RUNS THROUGH THIS WHOLE SYSTEM:\n//\n//   QUERY  — scope, search, filters, page. Lives here. NEVER persisted:\n//            restoring a stale search that renders an empty list is a bug\n//            wearing a feature's clothes.\n//   STYLE  — view mode, density, sort, page size, columns. Lives in\n//            lib/list-views (persisted per user, synced across devices).\n//\n// See features/agents/browse/FEATURE.md for the worked implementation and\n// lib/list-scope/FEATURE.md for the scope vocabulary + RPC template rules.\n\nimport type { ListScope, ListScopeKind } from \"./list-scope\";\nimport { exactCountTotal } from \"@ai-matrx/kit/count-total\";\nimport {\n  ALL_LANE_MEMBERS,\n  DEFAULT_LIST_SCOPE,\n  LIST_SCOPE_KINDS,\n  listSourceFilters,\n} from \"./list-scope\";\n\n/** Sentinel for \"has no value\" (uncategorized / untagged). Matches the SQL. */\nexport const NONE_VALUE = \"__none__\";\n\n/**\n * ONE filter vocabulary, shared by column headers and any filter panel.\n * Serialized straight into `<feature>_list_scoped(p_filters)`, so a filter set\n * from a column header and the same filter set from a panel are the same query\n * and cannot drift.\n *\n * Mirrors MatrxDataTable's ColumnFilterValue on purpose — the table is the\n * primary producer, and a translation layer with its own vocabulary would be a\n * third place for the two to disagree.\n */\nexport type EntityFilterValue =\n  | { kind: \"text\"; value: string }\n  | { kind: \"select\"; values: string[] }\n  | { kind: \"boolean\"; value: boolean };\n\n/** Keyed by COLUMN ID, so the server predicate and the header agree by name. */\nexport type EntityFilters = Record<string, EntityFilterValue>;\n\n/**\n * THE ARCHIVED-ITEMS LAW's tri-state (`active` | `archived` | `all`) — the same words as\n * `@ai-matrx/design-system`'s `ArchiveFilterValue`. Spelled here, not imported, so this headless\n * package never installs the UI kit for one type (2026-10-08); `entity-list.types.test.ts` fails\n * typecheck the moment the two vocabularies drift apart.\n */\nexport type ArchivedFilter = \"active\" | \"archived\" | \"all\";\n\nexport interface EntityListQuery {\n  /** The access LANE — how I can see a record (All, Mine, My team, …). */\n  scope: ListScope;\n  /**\n   * THE ORGANIZATION FILTER — which of my organizations to look at. Its own\n   * axis, never inside a lane: null = All organizations (the default on every\n   * load), else one organization id. URL `?org_filter=`. It narrows EVERY lane and\n   * the counts, and is sent to every list RPC as `p_org_id`\n   * (`listOrgParam(query)`). It never initializes from, syncs with, or writes\n   * to the ACTIVE organization (common-docs\n   * /policies/access-ladder.md).\n   */\n  orgId: string | null;\n  search: string;\n  /**\n   * Reach into full record content (prompt bodies, transcript text...).\n   * Opt-in: it is a full scan server-side and must never be the default.\n   */\n  deep: boolean;\n  /**\n   * Kept OUT of `filters` because it carries a DEFAULT (\"active only\") rather\n   * than being absent-means-unfiltered like every other column.\n   */\n  archived: ArchivedFilter;\n  filters: EntityFilters;\n  /** Display-visible lanes, kept out of URLs but carried to every source read. */\n  visibleScopes?: readonly ListScopeKind[];\n  /** One-based, matching the pagination UI. */\n  page: number;\n}\n\nexport const DEFAULT_ENTITY_LIST_QUERY: EntityListQuery = {\n  scope: DEFAULT_LIST_SCOPE,\n  orgId: null,\n  search: \"\",\n  deep: false,\n  archived: \"active\",\n  filters: {},\n  page: 1,\n};\n\n/**\n * How many things are narrowing the list right now (the Filters badge).\n * Counts ONLY what the user applied — never the sort or the active scope.\n * A badge that reads \"1\" on an untouched page is a permanent lie that trains\n * people to ignore the number (which is exactly what /agents/all's did).\n *\n * 🚨 THE ARCHIVE AXIS IS COMPARED AGAINST THE SURFACE'S OWN DEFAULT, NOT\n * AGAINST THE LITERAL `\"active\"`. THE ARCHIVED-ITEMS LAW §6 made the starting\n * value a user KNOB (`userPreferences.lists.archivedDefault`), so a user who\n * set theirs to \"Active + archived\" met an untouched page whose Filters badge\n * read \"1\" and whose empty state claimed a search-and-filter narrowing that\n * nobody had applied — the exact permanent lie this function exists to\n * prevent, reintroduced by the knob. The default is passed in because only the\n * hook knows it.\n */\nexport function countActiveFilters(\n  query: EntityListQuery,\n  defaultArchived: ArchivedFilter = DEFAULT_ENTITY_LIST_QUERY.archived,\n): number {\n  return (\n    Object.keys(query.filters).length +\n    (query.archived !== defaultArchived ? 1 : 0)\n  );\n}\n\n/**\n * 🚨 THE ALL-ARCHIVED FACT — what a list must know before it may say \"none\".\n *\n * THE DEFECT THIS CLOSES (row F10 repair, 2026-09-10). Every archive-aware\n * `EntityListConfig` carries a STATIC `emptyState`, and the shell fell back to\n * it whenever the query was untouched and no rows came back. But the archive\n * filter's default HIDES archived rows (THE ARCHIVED-ITEMS LAW §2), so \"the\n * live half is empty\" and \"there is nothing here\" are different facts — and\n * the shell was printing the second when it only knew the first. Measured on\n * `/maps` with all 46 of a user's maps archived: *\"No maps yet — A map is a\n * picture of how something works … Make one\"*, beside a \"New map\" button, one\n * popover away from that page's own Archived filter holding all 46. A screen\n * telling an Expert to rebuild work they already have.\n *\n * The count CANNOT come from the facets or the scope counts: both are fetched\n * with the query's CURRENT `archived` value, so on the defaulted page they\n * describe the live half too. It is its own read, `archived: \"archived\"`, and\n * it is asked ONLY when the live half came back empty — at most one extra,\n * page-size-1 request, on the one screen that would otherwise lie.\n */\nexport type ArchivedProbe =\n  /** Not applicable: the surface has no archive axis, or is already showing archived rows, or the live half is not empty. */\n  | { state: \"off\" }\n  /** The live half is empty and the archived count is in flight. Say nothing yet. */\n  | { state: \"loading\" }\n  /** Answered. `total` archived rows match this exact scope/search/filters. */\n  | { state: \"known\"; total: number; /** An open-ended archive: at least `total`, the store named no count. */ more?: boolean }\n  /** The read broke. The page must say it cannot tell — never fall back to \"none yet\". */\n  | { state: \"failed\" };\n\n/**\n * One narrowing choice inside a scope — an org under \"My Orgs\", an industry\n * under \"Industry\". The LABEL comes from the same query as the count.\n *\n * That is deliberate and load-bearing: reading names from a Redux slice meant\n * depending on `fetchFullContext`, a thunk that only runs on tasks/org-settings\n * surfaces — so on /agents/all the slice was empty and the My Orgs dropdown\n * silently never rendered. A tab bar must be self-sufficient from its own\n * query, with no hydration ordering to get wrong.\n */\nexport interface ScopeNarrowOption {\n  id: string;\n  label: string;\n  count: number;\n}\n\nexport interface EntityScopeCounts {\n  /** True server-side total per scope kind. Absent kinds are unsupported. */\n  byKind: Partial<Record<ListScopeKind, number>>;\n  /**\n   * The service names no lane counts for this question (an open-ended list the store pages\n   * without a count — `EntityListPage.hasMore`). The tabs draw no number at all, never `0`.\n   */\n  uncounted?: boolean;\n  /** Narrowing options per scope kind, in server order. */\n  narrow: Partial<Record<ListScopeKind, ScopeNarrowOption[]>>;\n  /**\n   * WHY a scope has no narrowing options, in a sentence a person can act on.\n   *\n   * 🚨 THE DEFECT THIS CLOSES (one-resolution FIX-R6/F1, 2026-09-08). On\n   * production `/mandates`, `narrow.orgs` came back empty and the declared\n   * **Organization** section simply was not rendered — an admin belonging to\n   * nine organizations saw a Filters panel that never mentioned organizations,\n   * with nothing on screen saying why. \"No options, so no section\" is the\n   * fourth law's silent failure: a declared narrowing is either offered or it\n   * says what happened.\n   *\n   * A service that CANNOT offer options — the counts were refused, the caller's\n   * organizations have not been read yet, the caller genuinely belongs to none\n   * — sets the reason here and the panel prints it. Absent + no options means\n   * the shell will say so in its own generic words rather than hide.\n   */\n  narrowUnavailable?: Partial<Record<ListScopeKind, string>>;\n}\n\nexport const EMPTY_SCOPE_COUNTS: EntityScopeCounts = {\n  byKind: {},\n  narrow: {},\n};\n\n/** One row of any `*_list_scope_counts` RPC. */\nexport interface ScopeCountRow {\n  scope: string;\n  narrow_id?: string | null;\n  label?: string | null;\n  total: number | string | null;\n}\n\n/**\n * THE ONE READER of a `*_list_scope_counts` result. Every surface converts\n * through here, so a scope the server learns (System, then My team) reaches its\n * tab everywhere at once. A hand-listed `kind !== \"mine\" && kind !== \"orgs\"…`\n * filter per service is how `system` came back with a real total and was\n * silently dropped on the way to its tab — the vocabulary check reads\n * LIST_SCOPE_KINDS and nothing else. A row with `narrow_id` is one\n * organization (or industry) inside its scope; without one it is the scope's\n * blended total.\n */\nexport function scopeCountsFromRows(\n  rows: readonly ScopeCountRow[] | null | undefined,\n  unnamed = \"Unnamed\",\n  visibleScopes?: readonly ListScopeKind[],\n): EntityScopeCounts {\n  const counts: EntityScopeCounts = { byKind: {}, narrow: {} };\n  for (const row of rows ?? []) {\n    const kind = LIST_SCOPE_KINDS.find((k) => k === row.scope);\n    if (!kind) continue;\n    if (row.total === null || row.total === undefined) continue;\n    const total = typeof row.total === \"string\"\n      ? (/^[0-9]+$/.test(row.total) ? Number(row.total) : NaN)\n      : row.total;\n    if (!Number.isSafeInteger(total) || total < 0) continue;\n    if (row.narrow_id) {\n      (counts.narrow[kind] ??= []).push({\n        id: row.narrow_id,\n        label: row.label ?? unnamed,\n        count: total,\n      });\n      continue;\n    }\n    counts.byKind[kind] = total;\n  }\n  if (visibleScopes) {\n    const all = deriveVisibleAllCount(counts.byKind, visibleScopes);\n    if (all === undefined) delete counts.byKind.all;\n    else counts.byKind.all = all;\n  } else {\n    // A server-provided personal All is not authoritative without the visible\n    // lane vector. Named admin aggregates retain their native semantics.\n    delete counts.byKind.all;\n  }\n  return counts;\n}\n\n/** Exact All count from the visible, composable lane counts; unknown is never zero. */\nexport function deriveVisibleAllCount(\n  byKind: Partial<Record<ListScopeKind, number | null | undefined>>,\n  visibleScopes: readonly ListScopeKind[] | undefined,\n): number | undefined {\n  if (!visibleScopes) return undefined;\n  const lanes = [...new Set(visibleScopes)].filter(\n    (kind): kind is ListScopeKind => ALL_LANE_MEMBERS.includes(kind),\n  );\n  return exactCountTotal(lanes.map((kind) => byKind[kind]));\n}\n\nexport { listSourceFilters };\n\n/**\n * Server-computed filter OPTIONS with counts, keyed by facet name.\n *\n * Server-computed is not a nicety: deriving \"which categories exist\" from\n * loaded rows means loading every row, which is the pattern this system\n * replaced. One live account has 34 categories and 773 distinct tags.\n */\nexport interface EntityFacets {\n  byKind: Record<string, { value: string; count: number }[]>;\n}\n\nexport const EMPTY_FACETS: EntityFacets = { byKind: {} };\n\n/** Count for one facet value, 0 when the facet or value is absent. */\nexport function facetCount(\n  facets: EntityFacets,\n  kind: string,\n  value: string,\n): number {\n  return facets.byKind[kind]?.find((v) => v.value === value)?.count ?? 0;\n}\n\n/** Options for one facet, most-used first (the RPC already orders them). */\nexport function facetValues(\n  facets: EntityFacets,\n  kind: string,\n): { value: string; count: number }[] {\n  return facets.byKind[kind] ?? [];\n}\n\n/**\n * A page of rows plus the TRUE total — not `rows.length`. Every\n * `*_list_scoped` RPC returns `total_count` as a window function over the\n * filtered set, identical on every row.\n */\nexport interface EntityListPage<TRow> {\n  rows: TRow[];\n  total: number;\n  /**\n   * AN OPEN-ENDED PAGE (TABLE-ACTIONS, 2026-10-03): the store pages this list and names no\n   * count. Present → `total` is only how many rows reach through THIS page, and `hasMore` says\n   * whether a next page exists; the pagers draw no \"of N\" and Next follows `hasMore`. Absent → a\n   * counted list, as always. A count is the store's or absent — never a full read to make one.\n   */\n  hasMore?: boolean;\n}\n\n/** Sort + paging, handed to the RPC. Style-owned, so it arrives from prefs. */\nexport interface EntityListSort {\n  /**\n   * Column id. Free string, not a closed union: every column a surface\n   * declares is sortable (app policy), so the valid set is that surface's\n   * column registry. The server whitelists it and falls back rather than\n   * erroring, so a stale stored value can never break a page.\n   */\n  sort: string;\n  direction: \"asc\" | \"desc\";\n  /** Pin favorites above every other ordering. */\n  favoritesFirst: boolean;\n  pageSize: number;\n}\n","/** The reserved filter-bag key shared by every list source. */\nexport const DIMENSION_FILTER_KEY = \"__dimension\";\ntype DimensionSelection = { kind:\"select\"; values:string[] };\nexport function dimensionValueOf(filters: Readonly<Record<string, unknown>>): string|null {\n  const value=filters[DIMENSION_FILTER_KEY];\n  if(!value || typeof value!==\"object\" || !(\"kind\" in value) || value.kind!==\"select\" || !(\"values\" in value) || !Array.isArray(value.values)) return null;\n  return typeof value.values[0]===\"string\" ? value.values[0] : null;\n}\n/** Preserve a host's extended filter vocabulary (including date windows). */\nexport function withDimensionValue<T extends {kind:\"text\"|\"select\"|\"boolean\"|\"date\"}=never>(filters:Record<string,T>,valueId:string|null): Record<string,T|DimensionSelection> {\n  const next:Record<string,T|DimensionSelection>={...filters};\n  if(valueId) next[DIMENSION_FILTER_KEY]={kind:\"select\",values:[valueId]};\n  else delete next[DIMENSION_FILTER_KEY];\n  return next;\n}\n"],"mappings":"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","names":[]}