@aglyn/plugins-marketplace 1.0.0-beta.231 → 1.0.0-beta.232

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/listing-content.component.js +2 -0
  3. package/src/lib/components/listing-content.component.js.map +1 -1
  4. package/src/lib/components/listing-detail-editor.component.js +1 -0
  5. package/src/lib/components/listing-detail-editor.component.js.map +1 -1
  6. package/src/lib/components/listing-detail-page.component.js +4 -0
  7. package/src/lib/components/listing-detail-page.component.js.map +1 -1
  8. package/src/lib/components/listing-review-status.component.js +1 -0
  9. package/src/lib/components/listing-review-status.component.js.map +1 -1
  10. package/src/lib/components/listing-reviews.component.js +1 -0
  11. package/src/lib/components/listing-reviews.component.js.map +1 -1
  12. package/src/lib/components/marketplace-browse.component.js +3 -0
  13. package/src/lib/components/marketplace-browse.component.js.map +1 -1
  14. package/src/lib/components/marketplace-hub.component.js +1 -0
  15. package/src/lib/components/marketplace-hub.component.js.map +1 -1
  16. package/src/lib/components/marketplace-reports-page.component.js +2 -0
  17. package/src/lib/components/marketplace-reports-page.component.js.map +1 -1
  18. package/src/lib/components/plugin-review-detail.component.js +11 -0
  19. package/src/lib/components/plugin-review-detail.component.js.map +1 -1
  20. package/src/lib/components/plugin-reviews-queue.component.js +2 -0
  21. package/src/lib/components/plugin-reviews-queue.component.js.map +1 -1
  22. package/src/lib/components/publish-plugin-form.component.js +1 -0
  23. package/src/lib/components/publish-plugin-form.component.js.map +1 -1
  24. package/src/lib/components/uninstall-impact-dialog.component.js +1 -0
  25. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-browse.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { mdiCheckDecagram } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Alert,\n Chip,\n Tooltip,\n Grid,\n MenuItem,\n Rating,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, getDoc } from 'firebase/firestore'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n buildRoute,\n type OrgPermissions,\n PLATFORM_BRAND_NAME,\n pluginDocsHelp,\n Route,\n} from '@aglyn/aglyn'\nimport {\n useConsoleHostRoute,\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n useHostOrgId,\n useScopeTokens,\n} from '@aglyn/tenant-feature-instance'\n/*\n * The MODULE, not the barrel, for the two PURE helpers — the specs that render\n * this grid mock `@aglyn/tenant-feature-instance` wholesale to stage their\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport {\n pluginRecordListQuery,\n pluginRecordsFromRows,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport {\n LISTING_CATEGORIES,\n listingArtifactType,\n listingArtifactLabel,\n resolvePluginInstallState,\n} from '../model/marketplace'\nimport {\n BROWSE_SORTS,\n type BrowseSort,\n browseBase,\n MARKETPLACE_BROWSE_QUERY,\n} from '../model/listing-query'\nimport { ListingImage } from './listing-image.component'\n\n// The console route table is shared (AGL-685), so these go through\n// buildRoute rather than being reassembled from a base string — the shape\n// of `/[orgSlug]/hosts/[host]/marketplace/…` is not this plugin's to know.\n\n/**\n * How many install records the shelf reads to decide what it already runs.\n *\n * One number for the five collections that answer that question, because the\n * card reads them together and a reader comparing a plugin's chip to a\n * dataset's should not have to know which window was twice the other. It\n * bounds an install-state LOOKUP, not the shelf: the listings query has its\n * own cap, and the grid pages what survives the filters.\n */\nconst INSTALL_STATE_CEILING = 100\n\n/** How the browse clauses read in a refusal. */\nconst BROWSE_HEADERS: Readonly<Record<string, string>> = { category: 'Category' }\n/** How long the search box waits for typing to stop before it asks again. */\nconst SEARCH_SETTLE_MS = 300\n\nexport interface MarketplaceBrowseProps {\n hostId: string\n /** Signed-in user's org permissions, supplied by the shell (AGL-395). */\n permissions?: Partial<OrgPermissions>\n /**\n * Rendered inside the org-scope `/marketplace` route (AGL-772) rather\n * than a site's marketplace tab. Only affects link targets — the grid\n * still installs through the acting `hostId` until targeting lands\n * (AGL-773) — so detail links resolve to the org route, not a per-site\n * one that is being retired.\n */\n orgScoped?: boolean\n /**\n * The acting org's slug from the URL (AGL-867). When given at org scope,\n * detail links build from it directly instead of the async\n * `hostIndex`→`orgs` resolution, which can return empty and leave the detail\n * page — the only place installs happen now — unreachable from browse.\n */\n orgSlug?: string\n /**\n * Restrict the grid to one publisher's listings (AGL-869), for the org-scope\n * publisher page. Omitted on the main browse, which shows everyone.\n */\n publisherId?: string\n}\n\n/**\n * Marketplace components browse (AGL-44). A read-only catalogue: each card links\n * to the listing's detail page, which is the only place an install happens\n * (AGL-867) — installing from a grid card was too easy and skipped the\n * site-targeting choice. The card still shows install STATE (installed,\n * org-wide) so the shelf reads honestly, but carries no install action.\n */\nexport function MarketplaceBrowse(props: MarketplaceBrowseProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { orgScoped, orgSlug: orgSlugProp, publisherId } = props\n const [handles, setHandles] = useState<Record<string, string>>({})\n // Listings are org-owned (AGL-652), so \"is this mine\" is an org comparison.\n // Resolved from the routing mirror rather than a new prop so the component\n // stays self-contained; hostIndex is signed-in readable. `undefined` while\n // it resolves: the shelf's query names the viewer's org in its audience\n // clause, so it waits for the answer rather than asking twice. A failed\n // lookup settles at `null` — the public shelf, without the viewer's own\n // listings in review.\n const [viewerOrgId, setViewerOrgId] = useState<string | null | undefined>(\n undefined,\n )\n useEffect(() => {\n let active = true\n setViewerOrgId(undefined)\n void getDoc(doc(firestore, 'hostIndex', hostId))\n .then((snapshot) => {\n if (active) setViewerOrgId((snapshot.get('orgId') as string) ?? null)\n })\n .catch(() => {\n if (active) setViewerOrgId(null)\n })\n return () => {\n active = false\n }\n }, [firestore, hostId])\n // Card links were built as `/{hostDocId}/marketplace/…`, a shape that has\n // not resolved since AGL-621/622 — every listing and publisher link on\n // this grid 404'd. One shared resolution (AGL-673); null renders plain\n // text rather than a link to nowhere.\n const { orgSlug: resolvedOrgSlug, subdomain } = useConsoleHostRoute(hostId)\n // Prefer the URL-supplied slug (AGL-867) — synchronous and always present at\n // org scope — over the async host resolution, which the per-site route still\n // relies on.\n const orgSlug = orgSlugProp ?? resolvedOrgSlug\n\n // Link targets differ by surface (AGL-772): the org marketplace resolves\n // to the org route (`/[orgSlug]/marketplace/[listingId]`), the per-site tab\n // to the host route. Null → plain text, never a link to nowhere. Publisher\n // pages have no org route yet, so they stay text at org scope for now.\n const listingHref = (listingId: string) =>\n orgScoped\n ? orgSlug\n ? buildRoute(Route.ORG_MARKETPLACE_LISTING, { orgSlug, listingId })\n : undefined\n : orgSlug && subdomain\n ? buildRoute(Route.ORG_MARKETPLACE_LISTING, {\n orgSlug,\n listingId,\n })\n : undefined\n // Storefront links carry the publisher's HANDLE (AGL-1001), the identity\n // shown right beside them on the card. The id remains a valid segment on\n // the page itself, so a publisher whose handle hasn't loaded yet still\n // links somewhere real rather than nowhere.\n const publisherHref = (profileId: string) =>\n orgScoped\n ? // Org-scope publisher storefront (AGL-869): all of one publisher's\n // listings. Needs the URL slug, which is passed in at org scope.\n orgSlug\n ? buildRoute(Route.ORG_MARKETPLACE_PUBLISHER, {\n orgSlug,\n handle: handles[profileId] ?? profileId,\n })\n : undefined\n : orgSlug && subdomain\n ? buildRoute(Route.ORG_MARKETPLACE_PUBLISHER, {\n orgSlug,\n handle: handles[profileId] ?? profileId,\n })\n : undefined\n\n /*\n * THE SHELF IS ITS QUERY (AGL-3321).\n *\n * Browse read the newest ninety listings and resolved everything else in\n * memory — the search box, the category chips, the three sorts, and the\n * gates that keep deleted, private and unreviewed listings off the shelf.\n * A listing that was not among those ninety could not be found by any of\n * them, and \"Most installed\" sorted a sample.\n *\n * Every one of them is on the query now. The gates are one clause, the\n * listing's written `browseAudience` (see `listingBrowseAudience`), asked\n * with `array-contains-any` for everyone (`*`) and the viewer's own org —\n * the owner exemption the in-memory gate carried (AGL-432): a publisher\n * sees their own listing while it waits on review. A publisher page asks\n * the same scopes joined to that publisher. The search folds into the same\n * clause through `browseTokens`, a category is an equality, and each sort\n * is the query's order; `MARKETPLACE_BROWSE_QUERY` declares them and its\n * spec pins the composites that serve them.\n *\n * `browseAudience` is MUTABLE — an unpublish, a takedown or a verdict\n * rewrites it — so a document can stop matching mid-session and be cached\n * as a `noDocument` tombstone at the path the detail page reads by id\n * (AGL-827/929). That was why the `deletedAt` predicate came off this query\n * in AGL-1196; `confirmDisappearances` is the repair that hook documents\n * for a predicate that cannot be dropped, and this one cannot.\n */\n const gridFilter = useListGridFilter({ selectFields: ['category'] })\n const [search, setSearch] = useState('')\n const [searchWords, setSearchWords] = useState<string[]>([])\n useEffect(() => {\n const timer = setTimeout(\n () => setSearchWords(search.split(/\\s+/).filter(Boolean)),\n SEARCH_SETTLE_MS,\n )\n return () => clearTimeout(timer)\n }, [search])\n const [sort, setSort] = useState<BrowseSort>('newest')\n const category =\n gridFilter.clauses.find((clause) => clause.field === 'category')?.value ??\n null\n const listingsRef = useMemo(\n () =>\n viewerOrgId === undefined\n ? null\n : collection(firestore, 'marketplaceListings'),\n [firestore, viewerOrgId],\n )\n const {\n rows: shown,\n status: shelfStatus,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n } = useListQuery<any>({\n collection: listingsRef,\n declaration: MARKETPLACE_BROWSE_QUERY,\n request: {\n clauses: gridFilter.clauses,\n search: searchWords,\n sort: BROWSE_SORTS[sort],\n base: browseBase(viewerOrgId, publisherId),\n },\n deps: [firestore, viewerOrgId, publisherId],\n idField: '$id',\n confirmDisappearances: true,\n })\n const listings = shown\n const filtering = Boolean(category) || searchWords.length > 0\n const refusals = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: MARKETPLACE_BROWSE_QUERY.fields,\n headers: BROWSE_HEADERS,\n }),\n [plan],\n )\n /*\n * ## The install-state lookups are ORDERED too, and they are not lists\n *\n * The five queries below (this one, the two pin collections, the org\n * datasets and the site's email templates) answer one question per card:\n * has this listing already been installed here. None of them is rendered —\n * each is folded into a map keyed by listing id — so the fix that suits the\n * shelf above does not suit them: there is no page to walk and no footer to\n * put a control in.\n *\n * What they share with it is the defect. A bare `limit` is answered in\n * DOCUMENT-ID order, so an unnamed window is an arbitrary slice of its\n * collection, and a lookup MISS is indistinguishable from a genuine\n * not-installed: a workspace past such a window is shown \"Add to this site\"\n * beside something it already runs, and the detail page — which reads its\n * own pins by id — disagrees with the grid it was reached from.\n *\n * `collectionCeiling` names the order and asks for one row past the\n * ceiling, which is what turns \"the window may have bitten\" into a fact the\n * shelf can state rather than a silence it cannot.\n */\n const { data: installedRead } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'components'),\n INSTALL_STATE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: installedDocs, truncated: installedTruncated } = useMemo(\n () => ceilingedWindow<any>(installedRead, INSTALL_STATE_CEILING),\n [installedRead],\n )\n\n // listingId → installed component doc (deleted installs don't count).\n const installed = useMemo(() => {\n const map: Record<string, any> = {}\n for (const definition of installedDocs ?? []) {\n const listingId = definition?.marketplace?.listingId\n if (listingId && !definition.deletedAt) map[listingId] = definition\n }\n return map\n }, [installedDocs])\n\n // Plugin installs are version PINS, not component snapshots (AGL-656): the\n // `components` map above never holds one, so a plugin already installed —\n // at host or org scope — showed \"Add to this site\". These two pin\n // collections are what the loader honors (a host pin shadows an org pin).\n const orgId = useHostOrgId(hostId)\n const { data: hostPinRead } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'installs'),\n INSTALL_STATE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: hostPinDocs, truncated: hostPinsTruncated } = useMemo(\n () => ceilingedWindow<any>(hostPinRead, INSTALL_STATE_CEILING),\n [hostPinRead],\n )\n // Held at null while `useHostOrgId` is in flight, never `orgs/-pending-`\n // (AGL-1440): the AGL-1047 comment seven lines below records the same\n // denial-every-mount shape for `useScopeTokens` — this listen had it too.\n const { data: orgPinRead } = useFirestoreCollection<any>(\n () =>\n orgId\n ? collectionCeiling(\n collection(firestore, 'orgs', orgId, 'installs'),\n INSTALL_STATE_CEILING,\n )\n : null,\n [firestore, orgId],\n { idField: '$id' },\n )\n const { rows: orgPinDocs, truncated: orgPinsTruncated } = useMemo(\n () => ceilingedWindow<any>(orgPinRead, INSTALL_STATE_CEILING),\n [orgPinRead],\n )\n // Scoped (AGL-1044): an unfiltered list is REJECTED for a scoped member,\n // not filtered, so this would error without the constraint.\n //\n // Wait for the member doc before listing (AGL-1047). `useScopeTokens`\n // reports `orgWide: true` while loading, so without `scopeLoaded` a scoped\n // collaborator's first render computes `needsScope: false` and sends an\n // UNFILTERED list that the AGL-1041 rules deny per document. It recovers\n // on the next render, which is what makes it easy to miss: the page looks\n // right and logs a denial every mount.\n const {\n tokens: scopeTokens,\n orgWide: viewerOrgWide,\n loaded: scopeLoaded,\n } = useScopeTokens(orgId ?? undefined)\n const needsScope = Boolean(orgId) && !viewerOrgWide\n // The AGL-657 types land in neither the components collection nor a pin\n // (AGL-789): a dataset schema becomes an org dataset, an email template a\n // draft version. Both installers stamp the source listing, so read those.\n /*\n * The datasets are the data plugin's, listed through the source it\n * publishes (AGL-3080) — narrowed, for a member who is not org-wide, to the\n * ones they may see, which the rules require of them — each with the\n * listing it was installed from.\n *\n * The ceiling is the shared one. It is not sized for the dataset\n * collection: it is sized for the number of LISTINGS whose install state a\n * shelf of ninety cards can describe, which is the same question its four\n * neighbours answer, so it is the same number.\n */\n const { data: datasetRead } = useFirestoreCollection<any>(\n () =>\n orgId && scopeLoaded\n ? pluginRecordListQuery('dataset', firestore, {\n orgId,\n memberScope: needsScope ? scopeTokens : null,\n limit: INSTALL_STATE_CEILING + 1,\n })\n : null,\n [firestore, orgId, scopeLoaded, needsScope, scopeTokens],\n // The member's narrowing reads `visibleTo`, which is MUTABLE — every\n // scope edit rewrites it — so this query can tombstone a dataset the way\n // AGL-827 tombstoned a host. The rule requires the constraint for anyone\n // who is not org-wide, so unlike the listings query above the predicate\n // cannot simply be dropped (AGL-1196).\n { idField: '$id', confirmDisappearances: true },\n )\n const { rows: datasetRows, truncated: datasetsTruncated } = useMemo(\n () => ceilingedWindow<any>(datasetRead, INSTALL_STATE_CEILING),\n [datasetRead],\n )\n const { data: emailRead } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'emailTemplates'),\n INSTALL_STATE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: emailDocs, truncated: emailsTruncated } = useMemo(\n () => ceilingedWindow<any>(emailRead, INSTALL_STATE_CEILING),\n [emailRead],\n )\n // A theme is not a document in a collection — it is a field on the site\n // (AGL-1020) — so its install is read from the host doc rather than from a\n // per-artifact query like the two above.\n const { data: hostDoc } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n { idField: '$id' },\n )\n\n // listingId → the newest install of it. A schema install deliberately makes\n // a NEW dataset every time, so this is genuinely one-to-many; the highest\n // installed version is what the card should speak for.\n const artifactInstalls = useMemo(() => {\n const map: Record<string, { version: string | null }> = {}\n const note = (listingId: unknown, version: unknown) => {\n const id = listingId ? String(listingId) : ''\n if (!id) return\n const next = version != null ? String(version) : null\n const seen = map[id]?.version\n if (!seen || (next && next > seen)) map[id] = { version: next }\n }\n for (const dataset of pluginRecordsFromRows('dataset', datasetRows)) {\n const origin = dataset.facts['installedFrom'] as { listingId?: unknown; version?: unknown } | null\n note(origin?.listingId, origin?.version)\n }\n for (const template of emailDocs ?? []) {\n if (template.deletedAt) continue\n note(template.installedFrom?.listingId, template.installedFrom?.version)\n }\n note(hostDoc?.themeInstalledFrom?.listingId, hostDoc?.themeInstalledFrom?.version)\n return map\n }, [datasetRows, emailDocs, hostDoc])\n\n const hostPins = useMemo(() => {\n const map: Record<string, any> = {}\n for (const pin of hostPinDocs ?? []) map[pin.$id] = pin\n return map\n }, [hostPinDocs])\n const orgPins = useMemo(() => {\n const map: Record<string, any> = {}\n for (const pin of orgPinDocs ?? []) map[pin.$id] = pin\n return map\n }, [orgPinDocs])\n\n // Resolve each listing's publisher handle once. `handles` must NOT be a\n // dependency here: the effect writes `handles`, so listing it would make the\n // effect re-run on its own output. During the post-load window, when\n // `listings` arrives while every other subscription on this always-mounted\n // grid is also settling, that self-retrigger adds render+effect cycles to an\n // already dense flurry — enough that a concurrent update elsewhere can trip\n // React's nested-update limit (AGL-785). A ref of already-requested ids\n // dedupes instead, so the effect depends only on `listings`.\n const requestedHandles = useRef<Set<string>>(new Set())\n useEffect(() => {\n const profileIds = [\n ...new Set((listings ?? []).map((listing: any) => listing.profileId)),\n ].filter(\n (profileId) =>\n profileId && !requestedHandles.current.has(String(profileId)),\n )\n if (!profileIds.length) return\n for (const profileId of profileIds)\n requestedHandles.current.add(String(profileId))\n let cancelled = false\n Promise.all(\n profileIds.map(async (profileId) => {\n const snapshot = await getDoc(\n doc(firestore, 'publisherProfiles', String(profileId)),\n ).catch(() => null)\n return [profileId, snapshot?.get('handle') ?? ''] as const\n }),\n ).then((entries) => {\n if (!cancelled) {\n setHandles((prev) => ({\n ...prev,\n ...Object.fromEntries(entries),\n }))\n }\n })\n return () => {\n cancelled = true\n }\n }, [listings, firestore])\n\n /**\n * The install-state lookups did not read everything they were asked about.\n *\n * Said on the SHELF rather than on the card that is wrong, because there is\n * no way to know which card that is: a miss and a genuine not-installed are\n * the same absence from the same map. What the reader can act on is that the\n * chips are a floor — anything marked installed is installed, and something\n * unmarked may still be.\n */\n const installStateTruncated =\n installedTruncated ||\n hostPinsTruncated ||\n orgPinsTruncated ||\n datasetsTruncated ||\n emailsTruncated\n\n return (\n <CardDisplay\n header={'Marketplace components'}\n help={pluginDocsHelp('plugins', { anchor: '#install--upgrade' })}\n contentGutterX\n contentGutterY\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ mb: 2, alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n <TextField\n placeholder=\"Search components…\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n size=\"small\"\n sx={{ minWidth: 200 }}\n />\n {/* The fixed taxonomy (AGL-430), not the categories the loaded page\n happened to hold: a chip is a question for the query, and one\n missing because no listing on this page had it could never be\n asked. */}\n {LISTING_CATEGORIES.map((value) => (\n <Chip\n key={value}\n label={value}\n variant={category === value ? 'filled' : 'outlined'}\n color={category === value ? 'primary' : 'default'}\n onClick={() =>\n gridFilter.setClauses(\n category === value\n ? []\n : [{ field: 'category', op: 'equals', value }],\n )\n }\n />\n ))}\n <TextField\n value={sort}\n onChange={(event) => setSort(event.target.value as BrowseSort)}\n size=\"small\"\n select\n sx={{ ml: 'auto', minWidth: 150 }}\n >\n <MenuItem value=\"newest\">{'Newest'}</MenuItem>\n <MenuItem value=\"installed\">{'Most installed'}</MenuItem>\n <MenuItem value=\"rated\">{'Highest rated'}</MenuItem>\n </TextField>\n </Stack>\n {installStateTruncated ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {`Installed state is resolved from the first ${INSTALL_STATE_CEILING} ` +\n 'install records in this workspace, ordered by id. There are ' +\n 'more, so a card may not be marked installed even though it is. ' +\n 'Open a listing to see what this workspace actually runs.'}\n </Alert>\n ) : null}\n <ListQueryNotices refused={refusals} notices={plan.notices} />\n {shown.length === 0 && page === 0 ? (\n shelfStatus === 'success' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {filtering\n ? 'No components match this search.'\n : 'No marketplace components published yet — publish one of ' +\n 'your reusable components from the Setup page to be the first.'}\n </Typography>\n ) : null\n ) : (\n <>\n <Grid container spacing={2}>\n {shown.map((listing: any) => {\n const artifactType = listingArtifactType(listing)\n const isPlugin = artifactType === 'plugin'\n const pluginState = resolvePluginInstallState(\n listing.latestVersion,\n isPlugin ? hostPins[listing.$id] : null,\n isPlugin ? orgPins[listing.$id] : null,\n )\n const componentInstall = installed[listing.$id]\n // datasetSchema/emailTemplate installs are tracked by the source\n // listing stamped on what they created (AGL-789).\n //\n // `emailStarter` is deliberately absent. Every other type has ONE\n // install per site to point at — a pin, a dataset, a draft on a\n // fixed catalog key, the site's theme — so \"Installed\" names\n // something. A starter installs as a NEW email each time, and a\n // site may keep five of them; there is no single copy for a chip\n // to describe, and marking the listing installed would grey out\n // the button that makes the sixth.\n const artifactInstall =\n artifactType === 'datasetSchema' ||\n artifactType === 'emailTemplate' ||\n artifactType === 'theme'\n ? artifactInstalls[listing.$id]\n : undefined\n const isInstalled = isPlugin\n ? pluginState.scope != null\n : Boolean(componentInstall ?? artifactInstall)\n const installedVersion = isPlugin\n ? pluginState.installedVersion\n : (componentInstall?.marketplace?.version ??\n artifactInstall?.version)\n const priceUsd = Number(listing.priceUsd ?? 0)\n const detailHref = listingHref(listing.$id)\n return (\n <Grid key={listing.$id} size={{ xs: 12, sm: 6, md: 4 }}>\n <Stack\n spacing={1}\n sx={{\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n p: 2,\n height: '100%',\n }}\n >\n {/* Resolved, never raw (AGL-1424) — the stored value may be\n a `media:` reference. */}\n <ListingImage\n src={listing.previewImageUrl}\n alt={`${listing.displayName} preview`}\n sx={{\n width: '100%',\n height: 120,\n objectFit: 'cover',\n borderRadius: 1,\n }}\n />\n {/* The name gets the whole line (AGL-1002). Sharing a row\n with the chips meant the title was the only thing that\n gave way when they did not fit — \"Promo Countdown\"\n truncated to \"Promo Coun…\" beside chips with room to\n spare, which inverts what matters on a browse card. */}\n <AppLink\n href={listingHref(listing.$id)}\n color=\"inherit\"\n underline=\"hover\"\n variant=\"subtitle2\"\n >\n {listing.displayName}\n </AppLink>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n {/* Primary classification, said first (AGL-864). */}\n <Chip\n size=\"small\"\n color=\"primary\"\n label={listingArtifactLabel(listing)}\n />\n {listing.category ? (\n <Chip size=\"small\" label={listing.category} />\n ) : null}\n {/* The reviewed badge belongs HERE most of all\n (AGL-1002): the detail page showed it, but browse is\n where someone is comparing options and deciding whose\n code to run.\n\n TWO CLAIMS, SHOWN SEPARATELY (AGL-1121). This chip\n vouches for the PUBLISHER; the one beside it says\n whether THESE bytes were reviewed. They were a single\n \"Verified\" chip driven by `reviewStatus`, which lives\n on the listing and deliberately survives a version\n bump — so a publisher verified on v1.0.0 could ship\n v1.9.0 containing anything and still be badged as\n though the code had been read.\n\n Both tooltips name `PLATFORM_BRAND_NAME` rather than\n the viewing org's `productName` (AGL-2351). They say\n who did the verifying and the reading — the\n deployment's own review queue, one catalog shared by\n every org — and a white-label agency's brand here would\n claim the agency vetted a publisher it has never heard\n of. */}\n {listing.reviewStatus === 'verified' ? (\n <Tooltip\n title={\n `A human at ${PLATFORM_BRAND_NAME} confirmed who ` +\n 'this publisher is, ' +\n 'and that the listing describes what the code does. ' +\n 'It is a claim about the publisher, not about this ' +\n 'release — it survives a version bump.'\n }\n >\n <Chip\n size=\"small\"\n color=\"info\"\n label=\"Verified publisher\"\n // Carries more weight than the neighbouring\n // classification chips on purpose: it is the only one\n // that says a human vouched for the code, and the\n // rest are just taxonomy. The icon is what makes it\n // findable when scanning a grid rather than reading\n // one card.\n icon={\n <MdiIcon\n path={mdiCheckDecagram.path}\n sx={{ fontSize: 16 }}\n />\n }\n sx={{ fontWeight: 600 }}\n />\n </Tooltip>\n ) : null}\n {/* The bytes on offer, not the person who wrote them.\n Absent on a listing published before this field\n existed, which reads as \"not reviewed\" — the safe\n direction for a claim about code. */}\n {listing.latestVersionReviewState === 'approved' ? (\n <Tooltip\n title={\n `A human at ${PLATFORM_BRAND_NAME} read these exact ` +\n 'bytes — the ' +\n 'version on offer — against a required checklist. ' +\n 'Re-earned per version, so a new release starts ' +\n 'without it. Not a security guarantee: every plugin ' +\n 'runs in the same sandbox either way.'\n }\n >\n <Chip size=\"small\" color=\"success\" label=\"Reviewed\" />\n </Tooltip>\n ) : null}\n {/*\n Price on EVERY card, `Free` included (AGL-2173). The\n chip only rendered above zero, so the four free\n listings the marketplace mockup shows carried no\n price at all — and \"no chip\" is the one reading a\n shopper cannot distinguish from \"we forgot\", on the\n row where the paid listing beside it says $29.\n */}\n <Chip\n size=\"small\"\n color={priceUsd > 0 ? 'primary' : 'success'}\n variant={priceUsd > 0 ? 'filled' : 'outlined'}\n label={priceUsd > 0 ? `$${priceUsd}` : 'Free'}\n />\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`v${listing.latestVersion}`}\n {handles[listing.profileId] ? (\n <>\n {' · by '}\n <AppLink\n href={publisherHref(listing.profileId)}\n color=\"primary\"\n underline=\"hover\"\n >\n {`@${handles[listing.profileId]}`}\n </AppLink>\n </>\n ) : (\n ''\n )}\n {listing.installCount\n ? ` · ${listing.installCount} install${\n listing.installCount === 1 ? '' : 's'\n }`\n : ''}\n </Typography>\n {/*\n Stars, as the mockup draws them (AGL-2173). The count\n stays alongside the average: \"5.0\" from one rating and\n from forty are not the same claim (AGL-655). An unrated\n listing says so rather than rendering nothing — silence\n read as \"no stars\", which is the opposite of \"not yet\n rated\".\n */}\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ alignItems: 'center' }}\n >\n {listing.ratingCount ? (\n <>\n <Rating\n size=\"small\"\n readOnly\n precision={0.1}\n value={Number(listing.ratingAverage ?? 0)}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${listing.ratingAverage ?? 0} (${listing.ratingCount})`}\n </Typography>\n </>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Not yet rated'}\n </Typography>\n )}\n </Stack>\n {listing.description ? (\n <Typography\n variant=\"body2\"\n color=\"text.secondary\"\n sx={{ flex: 1 }}\n // Four lines, then ellipsis (AGL-1002). Grid rows\n // stretch to their tallest card, so one publisher's\n // essay used to leave every card beside it mostly white\n // space; the full text is one click away.\n //\n // Plain `style`, not `sx`: emotion drops the\n // `-webkit-box` display value on the way through, and\n // without it the other three properties clamp nothing\n // (measured — `display` computed to `flow-root` while\n // line-clamp and box-orient came through fine). Nothing\n // here reads the theme, so there is nothing to lose.\n style={{\n display: '-webkit-box',\n WebkitBoxOrient: 'vertical',\n WebkitLineClamp: 4,\n overflow: 'hidden',\n }}\n >\n {listing.description}\n </Typography>\n ) : (\n <span style={{ flex: 1 }} />\n )}\n {/* Read-only state, then a link to the detail page — the\n only place an install happens (AGL-867). No install/buy\n action lives on the grid. */}\n {/* Install state reads as one statement (AGL-1002): the\n scope belongs with \"Installed\", not up in the chip row\n among the type and category pills, which say what the\n listing IS rather than what this workspace has done\n with it. Org-wide installs apply to every site\n (AGL-656). */}\n {isInstalled ? (\n <Stack\n direction=\"row\"\n spacing={0.75}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"caption\" color=\"success.main\">\n {`Installed${\n installedVersion ? ` (v${installedVersion})` : ''\n }`}\n </Typography>\n {isPlugin && pluginState.scope === 'org' ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={\n pluginState.shadowed\n ? 'Org-wide (shadowed)'\n : 'Org-wide'\n }\n />\n ) : null}\n </Stack>\n ) : null}\n <AppLink\n componentVariant=\"button\"\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n href={detailHref ?? ''}\n disabled={!detailHref}\n >\n {priceUsd > 0 && !isInstalled\n ? `View details · $${priceUsd}`\n : 'View details'}\n </AppLink>\n </Stack>\n </Grid>\n )\n })}\n </Grid>\n {/* The query pages the shelf, so the total is not known — only\n whether another page exists. */}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={shown.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </>\n )}\n </CardDisplay>\n )\n}\nMarketplaceBrowse.displayName = 'MarketplaceBrowse'\n\nexport default MarketplaceBrowse\n"],"names":["mdiCheckDecagram","AppLink","CardDisplay","MdiIcon","ListPagination","ListQueryNotices","listQueryRefusals","useListGridFilter","Alert","Chip","Tooltip","Grid","MenuItem","Rating","Stack","TextField","Typography","collection","doc","getDoc","useListQuery","useEffect","useMemo","useRef","useState","buildRoute","PLATFORM_BRAND_NAME","pluginDocsHelp","Route","useConsoleHostRoute","useFirestore","useFirestoreCollection","useFirestoreDoc","useHostOrgId","useScopeTokens","ceilingedWindow","collectionCeiling","pluginRecordListQuery","pluginRecordsFromRows","LISTING_CATEGORIES","listingArtifactType","listingArtifactLabel","resolvePluginInstallState","BROWSE_SORTS","browseBase","MARKETPLACE_BROWSE_QUERY","ListingImage","INSTALL_STATE_CEILING","BROWSE_HEADERS","category","SEARCH_SETTLE_MS","MarketplaceBrowse","props","gridFilter","hostId","firestore","orgScoped","orgSlug","orgSlugProp","publisherId","handles","setHandles","viewerOrgId","setViewerOrgId","undefined","active","then","snapshot","get","catch","resolvedOrgSlug","subdomain","listingHref","listingId","ORG_MARKETPLACE_LISTING","publisherHref","profileId","ORG_MARKETPLACE_PUBLISHER","handle","selectFields","search","setSearch","searchWords","setSearchWords","timer","setTimeout","split","filter","Boolean","clearTimeout","sort","setSort","clauses","find","clause","field","value","listingsRef","rows","shown","status","shelfStatus","hasMore","page","setPage","pageSize","setPageSize","plan","declaration","request","base","deps","idField","confirmDisappearances","listings","filtering","length","refusals","refused","fields","headers","data","installedRead","installedDocs","truncated","installedTruncated","installed","map","definition","marketplace","deletedAt","orgId","hostPinRead","hostPinDocs","hostPinsTruncated","orgPinRead","orgPinDocs","orgPinsTruncated","tokens","scopeTokens","orgWide","viewerOrgWide","loaded","scopeLoaded","needsScope","datasetRead","memberScope","limit","datasetRows","datasetsTruncated","emailRead","emailDocs","emailsTruncated","hostDoc","artifactInstalls","note","version","id","String","next","seen","dataset","origin","facts","template","installedFrom","themeInstalledFrom","hostPins","pin","$id","orgPins","requestedHandles","Set","profileIds","listing","current","has","add","cancelled","Promise","all","entries","prev","Object","fromEntries","installStateTruncated","header","help","anchor","contentGutterX","contentGutterY","direction","spacing","sx","mb","alignItems","flexWrap","rowGap","placeholder","onChange","event","target","size","minWidth","label","variant","color","onClick","setClauses","op","select","ml","severity","notices","container","componentInstall","artifactType","isPlugin","pluginState","latestVersion","artifactInstall","isInstalled","scope","installedVersion","priceUsd","Number","detailHref","xs","sm","md","border","borderColor","borderRadius","p","height","src","previewImageUrl","alt","displayName","width","objectFit","href","underline","reviewStatus","title","icon","path","fontSize","fontWeight","latestVersionReviewState","installCount","ratingCount","readOnly","precision","ratingAverage","description","flex","style","display","WebkitBoxOrient","WebkitLineClamp","overflow","span","shadowed","componentVariant","disabled","rowCount","onPageChange","onPageSizeChange"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-browse.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { mdiCheckDecagram } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Alert,\n Chip,\n Tooltip,\n Grid,\n MenuItem,\n Rating,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, getDoc } from 'firebase/firestore'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n buildRoute,\n type OrgPermissions,\n PLATFORM_BRAND_NAME,\n pluginDocsHelp,\n Route,\n} from '@aglyn/aglyn'\nimport {\n useConsoleHostRoute,\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n useHostOrgId,\n useScopeTokens,\n} from '@aglyn/tenant-feature-instance'\n/*\n * The MODULE, not the barrel, for the two PURE helpers — the specs that render\n * this grid mock `@aglyn/tenant-feature-instance` wholesale to stage their\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport {\n pluginRecordListQuery,\n pluginRecordsFromRows,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport {\n LISTING_CATEGORIES,\n listingArtifactType,\n listingArtifactLabel,\n resolvePluginInstallState,\n} from '../model/marketplace'\nimport {\n BROWSE_SORTS,\n type BrowseSort,\n browseBase,\n MARKETPLACE_BROWSE_QUERY,\n} from '../model/listing-query'\nimport { ListingImage } from './listing-image.component'\n\n// The console route table is shared (AGL-685), so these go through\n// buildRoute rather than being reassembled from a base string — the shape\n// of `/[orgSlug]/hosts/[host]/marketplace/…` is not this plugin's to know.\n\n/**\n * How many install records the shelf reads to decide what it already runs.\n *\n * One number for the five collections that answer that question, because the\n * card reads them together and a reader comparing a plugin's chip to a\n * dataset's should not have to know which window was twice the other. It\n * bounds an install-state LOOKUP, not the shelf: the listings query has its\n * own cap, and the grid pages what survives the filters.\n */\nconst INSTALL_STATE_CEILING = 100\n\n/** How the browse clauses read in a refusal. */\nconst BROWSE_HEADERS: Readonly<Record<string, string>> = { category: 'Category' }\n/** How long the search box waits for typing to stop before it asks again. */\nconst SEARCH_SETTLE_MS = 300\n\nexport interface MarketplaceBrowseProps {\n hostId: string\n /** Signed-in user's org permissions, supplied by the shell (AGL-395). */\n permissions?: Partial<OrgPermissions>\n /**\n * Rendered inside the org-scope `/marketplace` route (AGL-772) rather\n * than a site's marketplace tab. Only affects link targets — the grid\n * still installs through the acting `hostId` until targeting lands\n * (AGL-773) — so detail links resolve to the org route, not a per-site\n * one that is being retired.\n */\n orgScoped?: boolean\n /**\n * The acting org's slug from the URL (AGL-867). When given at org scope,\n * detail links build from it directly instead of the async\n * `hostIndex`→`orgs` resolution, which can return empty and leave the detail\n * page — the only place installs happen now — unreachable from browse.\n */\n orgSlug?: string\n /**\n * Restrict the grid to one publisher's listings (AGL-869), for the org-scope\n * publisher page. Omitted on the main browse, which shows everyone.\n */\n publisherId?: string\n}\n\n/**\n * Marketplace components browse (AGL-44). A read-only catalogue: each card links\n * to the listing's detail page, which is the only place an install happens\n * (AGL-867) — installing from a grid card was too easy and skipped the\n * site-targeting choice. The card still shows install STATE (installed,\n * org-wide) so the shelf reads honestly, but carries no install action.\n */\nexport function MarketplaceBrowse(props: MarketplaceBrowseProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { orgScoped, orgSlug: orgSlugProp, publisherId } = props\n const [handles, setHandles] = useState<Record<string, string>>({})\n // Listings are org-owned (AGL-652), so \"is this mine\" is an org comparison.\n // Resolved from the routing mirror rather than a new prop so the component\n // stays self-contained; hostIndex is signed-in readable. `undefined` while\n // it resolves: the shelf's query names the viewer's org in its audience\n // clause, so it waits for the answer rather than asking twice. A failed\n // lookup settles at `null` — the public shelf, without the viewer's own\n // listings in review.\n const [viewerOrgId, setViewerOrgId] = useState<string | null | undefined>(\n undefined,\n )\n useEffect(() => {\n let active = true\n setViewerOrgId(undefined)\n void getDoc(doc(firestore, 'hostIndex', hostId))\n .then((snapshot) => {\n if (active) setViewerOrgId((snapshot.get('orgId') as string) ?? null)\n })\n .catch(() => {\n if (active) setViewerOrgId(null)\n })\n return () => {\n active = false\n }\n }, [firestore, hostId])\n // Card links were built as `/{hostDocId}/marketplace/…`, a shape that has\n // not resolved since AGL-621/622 — every listing and publisher link on\n // this grid 404'd. One shared resolution (AGL-673); null renders plain\n // text rather than a link to nowhere.\n const { orgSlug: resolvedOrgSlug, subdomain } = useConsoleHostRoute(hostId)\n // Prefer the URL-supplied slug (AGL-867) — synchronous and always present at\n // org scope — over the async host resolution, which the per-site route still\n // relies on.\n const orgSlug = orgSlugProp ?? resolvedOrgSlug\n\n // Link targets differ by surface (AGL-772): the org marketplace resolves\n // to the org route (`/[orgSlug]/marketplace/[listingId]`), the per-site tab\n // to the host route. Null → plain text, never a link to nowhere. Publisher\n // pages have no org route yet, so they stay text at org scope for now.\n const listingHref = (listingId: string) =>\n orgScoped\n ? orgSlug\n ? buildRoute(Route.ORG_MARKETPLACE_LISTING, { orgSlug, listingId })\n : undefined\n : orgSlug && subdomain\n ? buildRoute(Route.ORG_MARKETPLACE_LISTING, {\n orgSlug,\n listingId,\n })\n : undefined\n // Storefront links carry the publisher's HANDLE (AGL-1001), the identity\n // shown right beside them on the card. The id remains a valid segment on\n // the page itself, so a publisher whose handle hasn't loaded yet still\n // links somewhere real rather than nowhere.\n const publisherHref = (profileId: string) =>\n orgScoped\n ? // Org-scope publisher storefront (AGL-869): all of one publisher's\n // listings. Needs the URL slug, which is passed in at org scope.\n orgSlug\n ? buildRoute(Route.ORG_MARKETPLACE_PUBLISHER, {\n orgSlug,\n handle: handles[profileId] ?? profileId,\n })\n : undefined\n : orgSlug && subdomain\n ? buildRoute(Route.ORG_MARKETPLACE_PUBLISHER, {\n orgSlug,\n handle: handles[profileId] ?? profileId,\n })\n : undefined\n\n /*\n * THE SHELF IS ITS QUERY (AGL-3321).\n *\n * Browse read the newest ninety listings and resolved everything else in\n * memory — the search box, the category chips, the three sorts, and the\n * gates that keep deleted, private and unreviewed listings off the shelf.\n * A listing that was not among those ninety could not be found by any of\n * them, and \"Most installed\" sorted a sample.\n *\n * Every one of them is on the query now. The gates are one clause, the\n * listing's written `browseAudience` (see `listingBrowseAudience`), asked\n * with `array-contains-any` for everyone (`*`) and the viewer's own org —\n * the owner exemption the in-memory gate carried (AGL-432): a publisher\n * sees their own listing while it waits on review. A publisher page asks\n * the same scopes joined to that publisher. The search folds into the same\n * clause through `browseTokens`, a category is an equality, and each sort\n * is the query's order; `MARKETPLACE_BROWSE_QUERY` declares them and its\n * spec pins the composites that serve them.\n *\n * `browseAudience` is MUTABLE — an unpublish, a takedown or a verdict\n * rewrites it — so a document can stop matching mid-session and be cached\n * as a `noDocument` tombstone at the path the detail page reads by id\n * (AGL-827/929). That was why the `deletedAt` predicate came off this query\n * in AGL-1196; `confirmDisappearances` is the repair that hook documents\n * for a predicate that cannot be dropped, and this one cannot.\n */\n const gridFilter = useListGridFilter({ selectFields: ['category'] })\n const [search, setSearch] = useState('')\n const [searchWords, setSearchWords] = useState<string[]>([])\n useEffect(() => {\n const timer = setTimeout(\n () => setSearchWords(search.split(/\\s+/).filter(Boolean)),\n SEARCH_SETTLE_MS,\n )\n return () => clearTimeout(timer)\n }, [search])\n const [sort, setSort] = useState<BrowseSort>('newest')\n const category =\n gridFilter.clauses.find((clause) => clause.field === 'category')?.value ??\n null\n const listingsRef = useMemo(\n () =>\n viewerOrgId === undefined\n ? null\n : collection(firestore, 'marketplaceListings'),\n [firestore, viewerOrgId],\n )\n const {\n rows: shown,\n status: shelfStatus,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n } = useListQuery<any>({\n collection: listingsRef,\n declaration: MARKETPLACE_BROWSE_QUERY,\n request: {\n clauses: gridFilter.clauses,\n search: searchWords,\n sort: BROWSE_SORTS[sort],\n base: browseBase(viewerOrgId, publisherId),\n },\n deps: [firestore, viewerOrgId, publisherId],\n idField: '$id',\n confirmDisappearances: true,\n })\n const listings = shown\n const filtering = Boolean(category) || searchWords.length > 0\n const refusals = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: MARKETPLACE_BROWSE_QUERY.fields,\n headers: BROWSE_HEADERS,\n }),\n [plan],\n )\n /*\n * ## The install-state lookups are ORDERED too, and they are not lists\n *\n * The five queries below (this one, the two pin collections, the org\n * datasets and the site's email templates) answer one question per card:\n * has this listing already been installed here. None of them is rendered —\n * each is folded into a map keyed by listing id — so the fix that suits the\n * shelf above does not suit them: there is no page to walk and no footer to\n * put a control in.\n *\n * What they share with it is the defect. A bare `limit` is answered in\n * DOCUMENT-ID order, so an unnamed window is an arbitrary slice of its\n * collection, and a lookup MISS is indistinguishable from a genuine\n * not-installed: a workspace past such a window is shown \"Add to this site\"\n * beside something it already runs, and the detail page — which reads its\n * own pins by id — disagrees with the grid it was reached from.\n *\n * `collectionCeiling` names the order and asks for one row past the\n * ceiling, which is what turns \"the window may have bitten\" into a fact the\n * shelf can state rather than a silence it cannot.\n */\n const { data: installedRead } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'components'),\n INSTALL_STATE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: installedDocs, truncated: installedTruncated } = useMemo(\n () => ceilingedWindow<any>(installedRead, INSTALL_STATE_CEILING),\n [installedRead],\n )\n\n // listingId → installed component doc (deleted installs don't count).\n const installed = useMemo(() => {\n const map: Record<string, any> = {}\n for (const definition of installedDocs ?? []) {\n const listingId = definition?.marketplace?.listingId\n if (listingId && !definition.deletedAt) map[listingId] = definition\n }\n return map\n }, [installedDocs])\n\n // Plugin installs are version PINS, not component snapshots (AGL-656): the\n // `components` map above never holds one, so a plugin already installed —\n // at host or org scope — showed \"Add to this site\". These two pin\n // collections are what the loader honors (a host pin shadows an org pin).\n const orgId = useHostOrgId(hostId)\n const { data: hostPinRead } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'installs'),\n INSTALL_STATE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: hostPinDocs, truncated: hostPinsTruncated } = useMemo(\n () => ceilingedWindow<any>(hostPinRead, INSTALL_STATE_CEILING),\n [hostPinRead],\n )\n // Held at null while `useHostOrgId` is in flight, never `orgs/-pending-`\n // (AGL-1440): the AGL-1047 comment seven lines below records the same\n // denial-every-mount shape for `useScopeTokens` — this listen had it too.\n const { data: orgPinRead } = useFirestoreCollection<any>(\n () =>\n orgId\n ? collectionCeiling(\n collection(firestore, 'orgs', orgId, 'installs'),\n INSTALL_STATE_CEILING,\n )\n : null,\n [firestore, orgId],\n { idField: '$id' },\n )\n const { rows: orgPinDocs, truncated: orgPinsTruncated } = useMemo(\n () => ceilingedWindow<any>(orgPinRead, INSTALL_STATE_CEILING),\n [orgPinRead],\n )\n // Scoped (AGL-1044): an unfiltered list is REJECTED for a scoped member,\n // not filtered, so this would error without the constraint.\n //\n // Wait for the member doc before listing (AGL-1047). `useScopeTokens`\n // reports `orgWide: true` while loading, so without `scopeLoaded` a scoped\n // collaborator's first render computes `needsScope: false` and sends an\n // UNFILTERED list that the AGL-1041 rules deny per document. It recovers\n // on the next render, which is what makes it easy to miss: the page looks\n // right and logs a denial every mount.\n const {\n tokens: scopeTokens,\n orgWide: viewerOrgWide,\n loaded: scopeLoaded,\n } = useScopeTokens(orgId ?? undefined)\n const needsScope = Boolean(orgId) && !viewerOrgWide\n // The AGL-657 types land in neither the components collection nor a pin\n // (AGL-789): a dataset schema becomes an org dataset, an email template a\n // draft version. Both installers stamp the source listing, so read those.\n /*\n * The datasets are the data plugin's, listed through the source it\n * publishes (AGL-3080) — narrowed, for a member who is not org-wide, to the\n * ones they may see, which the rules require of them — each with the\n * listing it was installed from.\n *\n * The ceiling is the shared one. It is not sized for the dataset\n * collection: it is sized for the number of LISTINGS whose install state a\n * shelf of ninety cards can describe, which is the same question its four\n * neighbours answer, so it is the same number.\n */\n const { data: datasetRead } = useFirestoreCollection<any>(\n () =>\n orgId && scopeLoaded\n ? pluginRecordListQuery('dataset', firestore, {\n orgId,\n memberScope: needsScope ? scopeTokens : null,\n limit: INSTALL_STATE_CEILING + 1,\n })\n : null,\n [firestore, orgId, scopeLoaded, needsScope, scopeTokens],\n // The member's narrowing reads `visibleTo`, which is MUTABLE — every\n // scope edit rewrites it — so this query can tombstone a dataset the way\n // AGL-827 tombstoned a host. The rule requires the constraint for anyone\n // who is not org-wide, so unlike the listings query above the predicate\n // cannot simply be dropped (AGL-1196).\n { idField: '$id', confirmDisappearances: true },\n )\n const { rows: datasetRows, truncated: datasetsTruncated } = useMemo(\n () => ceilingedWindow<any>(datasetRead, INSTALL_STATE_CEILING),\n [datasetRead],\n )\n const { data: emailRead } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'emailTemplates'),\n INSTALL_STATE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: emailDocs, truncated: emailsTruncated } = useMemo(\n () => ceilingedWindow<any>(emailRead, INSTALL_STATE_CEILING),\n [emailRead],\n )\n // A theme is not a document in a collection — it is a field on the site\n // (AGL-1020) — so its install is read from the host doc rather than from a\n // per-artifact query like the two above.\n const { data: hostDoc } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n { idField: '$id' },\n )\n\n // listingId → the newest install of it. A schema install deliberately makes\n // a NEW dataset every time, so this is genuinely one-to-many; the highest\n // installed version is what the card should speak for.\n const artifactInstalls = useMemo(() => {\n const map: Record<string, { version: string | null }> = {}\n const note = (listingId: unknown, version: unknown) => {\n const id = listingId ? String(listingId) : ''\n if (!id) return\n const next = version != null ? String(version) : null\n const seen = map[id]?.version\n if (!seen || (next && next > seen)) map[id] = { version: next }\n }\n for (const dataset of pluginRecordsFromRows('dataset', datasetRows)) {\n const origin = dataset.facts['installedFrom'] as { listingId?: unknown; version?: unknown } | null\n note(origin?.listingId, origin?.version)\n }\n for (const template of emailDocs ?? []) {\n if (template.deletedAt) continue\n note(template.installedFrom?.listingId, template.installedFrom?.version)\n }\n note(hostDoc?.themeInstalledFrom?.listingId, hostDoc?.themeInstalledFrom?.version)\n return map\n }, [datasetRows, emailDocs, hostDoc])\n\n const hostPins = useMemo(() => {\n const map: Record<string, any> = {}\n for (const pin of hostPinDocs ?? []) map[pin.$id] = pin\n return map\n }, [hostPinDocs])\n const orgPins = useMemo(() => {\n const map: Record<string, any> = {}\n for (const pin of orgPinDocs ?? []) map[pin.$id] = pin\n return map\n }, [orgPinDocs])\n\n // Resolve each listing's publisher handle once. `handles` must NOT be a\n // dependency here: the effect writes `handles`, so listing it would make the\n // effect re-run on its own output. During the post-load window, when\n // `listings` arrives while every other subscription on this always-mounted\n // grid is also settling, that self-retrigger adds render+effect cycles to an\n // already dense flurry — enough that a concurrent update elsewhere can trip\n // React's nested-update limit (AGL-785). A ref of already-requested ids\n // dedupes instead, so the effect depends only on `listings`.\n const requestedHandles = useRef<Set<string>>(new Set())\n useEffect(() => {\n const profileIds = [\n ...new Set((listings ?? []).map((listing: any) => listing.profileId)),\n ].filter(\n (profileId) =>\n profileId && !requestedHandles.current.has(String(profileId)),\n )\n if (!profileIds.length) return\n for (const profileId of profileIds)\n requestedHandles.current.add(String(profileId))\n let cancelled = false\n Promise.all(\n profileIds.map(async (profileId) => {\n const snapshot = await getDoc(\n doc(firestore, 'publisherProfiles', String(profileId)),\n ).catch(() => null)\n return [profileId, snapshot?.get('handle') ?? ''] as const\n }),\n ).then((entries) => {\n if (!cancelled) {\n setHandles((prev) => ({\n ...prev,\n ...Object.fromEntries(entries),\n }))\n }\n })\n return () => {\n cancelled = true\n }\n }, [listings, firestore])\n\n /**\n * The install-state lookups did not read everything they were asked about.\n *\n * Said on the SHELF rather than on the card that is wrong, because there is\n * no way to know which card that is: a miss and a genuine not-installed are\n * the same absence from the same map. What the reader can act on is that the\n * chips are a floor — anything marked installed is installed, and something\n * unmarked may still be.\n */\n const installStateTruncated =\n installedTruncated ||\n hostPinsTruncated ||\n orgPinsTruncated ||\n datasetsTruncated ||\n emailsTruncated\n\n return (\n <CardDisplay\n header={'Marketplace components'}\n help={pluginDocsHelp('plugins', { anchor: '#install--upgrade' })}\n contentGutterX\n contentGutterY\n >\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ mb: 2, alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n <TextField\n placeholder=\"Search components…\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n size=\"small\"\n sx={{ minWidth: 200 }}\n />\n {/* The fixed taxonomy (AGL-430), not the categories the loaded page\n happened to hold: a chip is a question for the query, and one\n missing because no listing on this page had it could never be\n asked. */}\n {LISTING_CATEGORIES.map((value) => (\n <Chip\n key={value}\n label={value}\n variant={category === value ? 'filled' : 'outlined'}\n color={category === value ? 'primary' : 'default'}\n onClick={() =>\n gridFilter.setClauses(\n category === value\n ? []\n : [{ field: 'category', op: 'equals', value }],\n )\n }\n />\n ))}\n <TextField\n value={sort}\n onChange={(event) => setSort(event.target.value as BrowseSort)}\n size=\"small\"\n select\n sx={{ ml: 'auto', minWidth: 150 }}\n >\n <MenuItem value=\"newest\">{'Newest'}</MenuItem>\n <MenuItem value=\"installed\">{'Most installed'}</MenuItem>\n <MenuItem value=\"rated\">{'Highest rated'}</MenuItem>\n </TextField>\n </Stack>\n {installStateTruncated ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {`Installed state is resolved from the first ${INSTALL_STATE_CEILING} ` +\n 'install records in this workspace, ordered by id. There are ' +\n 'more, so a card may not be marked installed even though it is. ' +\n 'Open a listing to see what this workspace actually runs.'}\n </Alert>\n ) : null}\n <ListQueryNotices refused={refusals} notices={plan.notices} />\n {shown.length === 0 && page === 0 ? (\n shelfStatus === 'success' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {filtering\n ? 'No components match this search.'\n : 'No marketplace components published yet — publish one of ' +\n 'your reusable components from the Setup page to be the first.'}\n </Typography>\n ) : null\n ) : (\n <>\n <Grid container spacing={2}>\n {shown.map((listing: any) => {\n const artifactType = listingArtifactType(listing)\n const isPlugin = artifactType === 'plugin'\n const pluginState = resolvePluginInstallState(\n listing.latestVersion,\n isPlugin ? hostPins[listing.$id] : null,\n isPlugin ? orgPins[listing.$id] : null,\n )\n const componentInstall = installed[listing.$id]\n // datasetSchema/emailTemplate installs are tracked by the source\n // listing stamped on what they created (AGL-789).\n //\n // `emailStarter` is deliberately absent. Every other type has ONE\n // install per site to point at — a pin, a dataset, a draft on a\n // fixed catalog key, the site's theme — so \"Installed\" names\n // something. A starter installs as a NEW email each time, and a\n // site may keep five of them; there is no single copy for a chip\n // to describe, and marking the listing installed would grey out\n // the button that makes the sixth.\n const artifactInstall =\n artifactType === 'datasetSchema' ||\n artifactType === 'emailTemplate' ||\n artifactType === 'theme'\n ? artifactInstalls[listing.$id]\n : undefined\n const isInstalled = isPlugin\n ? pluginState.scope != null\n : Boolean(componentInstall ?? artifactInstall)\n const installedVersion = isPlugin\n ? pluginState.installedVersion\n : (componentInstall?.marketplace?.version ??\n artifactInstall?.version)\n const priceUsd = Number(listing.priceUsd ?? 0)\n const detailHref = listingHref(listing.$id)\n return (\n <Grid key={listing.$id} size={{ xs: 12, sm: 6, md: 4 }}>\n <Stack\n spacing={1}\n sx={{\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n p: 2,\n height: '100%',\n }}\n >\n {/* Resolved, never raw (AGL-1424) — the stored value may be\n a `media:` reference. */}\n <ListingImage\n src={listing.previewImageUrl}\n alt={`${listing.displayName} preview`}\n sx={{\n width: '100%',\n height: 120,\n objectFit: 'cover',\n borderRadius: 1,\n }}\n />\n {/* The name gets the whole line (AGL-1002). Sharing a row\n with the chips meant the title was the only thing that\n gave way when they did not fit — \"Promo Countdown\"\n truncated to \"Promo Coun…\" beside chips with room to\n spare, which inverts what matters on a browse card. */}\n <AppLink\n href={listingHref(listing.$id)}\n color=\"inherit\"\n underline=\"hover\"\n variant=\"subtitle2\"\n >\n {listing.displayName}\n </AppLink>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n {/* Primary classification, said first (AGL-864). */}\n <Chip\n size=\"small\"\n color=\"primary\"\n label={listingArtifactLabel(listing)}\n />\n {listing.category ? (\n <Chip size=\"small\" label={listing.category} />\n ) : null}\n {/* The reviewed badge belongs HERE most of all\n (AGL-1002): the detail page showed it, but browse is\n where someone is comparing options and deciding whose\n code to run.\n\n TWO CLAIMS, SHOWN SEPARATELY (AGL-1121). This chip\n vouches for the PUBLISHER; the one beside it says\n whether THESE bytes were reviewed. They were a single\n \"Verified\" chip driven by `reviewStatus`, which lives\n on the listing and deliberately survives a version\n bump — so a publisher verified on v1.0.0 could ship\n v1.9.0 containing anything and still be badged as\n though the code had been read.\n\n Both tooltips name `PLATFORM_BRAND_NAME` rather than\n the viewing org's `productName` (AGL-2351). They say\n who did the verifying and the reading — the\n deployment's own review queue, one catalog shared by\n every org — and a white-label agency's brand here would\n claim the agency vetted a publisher it has never heard\n of. */}\n {listing.reviewStatus === 'verified' ? (\n <Tooltip\n title={\n `A human at ${PLATFORM_BRAND_NAME} confirmed who ` +\n 'this publisher is, ' +\n 'and that the listing describes what the code does. ' +\n 'It is a claim about the publisher, not about this ' +\n 'release — it survives a version bump.'\n }\n >\n <Chip\n size=\"small\"\n color=\"info\"\n label=\"Verified publisher\"\n // Carries more weight than the neighbouring\n // classification chips on purpose: it is the only one\n // that says a human vouched for the code, and the\n // rest are just taxonomy. The icon is what makes it\n // findable when scanning a grid rather than reading\n // one card.\n icon={\n <MdiIcon\n path={mdiCheckDecagram.path}\n sx={{ fontSize: 16 }}\n />\n }\n sx={{ fontWeight: 600 }}\n />\n </Tooltip>\n ) : null}\n {/* The bytes on offer, not the person who wrote them.\n Absent on a listing published before this field\n existed, which reads as \"not reviewed\" — the safe\n direction for a claim about code. */}\n {listing.latestVersionReviewState === 'approved' ? (\n <Tooltip\n title={\n `A human at ${PLATFORM_BRAND_NAME} read these exact ` +\n 'bytes — the ' +\n 'version on offer — against a required checklist. ' +\n 'Re-earned per version, so a new release starts ' +\n 'without it. Not a security guarantee: every plugin ' +\n 'runs in the same sandbox either way.'\n }\n >\n <Chip size=\"small\" color=\"success\" label=\"Reviewed\" />\n </Tooltip>\n ) : null}\n {/*\n Price on EVERY card, `Free` included (AGL-2173). The\n chip only rendered above zero, so the four free\n listings the marketplace mockup shows carried no\n price at all — and \"no chip\" is the one reading a\n shopper cannot distinguish from \"we forgot\", on the\n row where the paid listing beside it says $29.\n */}\n <Chip\n size=\"small\"\n color={priceUsd > 0 ? 'primary' : 'success'}\n variant={priceUsd > 0 ? 'filled' : 'outlined'}\n label={priceUsd > 0 ? `$${priceUsd}` : 'Free'}\n />\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`v${listing.latestVersion}`}\n {handles[listing.profileId] ? (\n <>\n {' · by '}\n <AppLink\n href={publisherHref(listing.profileId)}\n color=\"primary\"\n underline=\"hover\"\n >\n {`@${handles[listing.profileId]}`}\n </AppLink>\n </>\n ) : (\n ''\n )}\n {listing.installCount\n ? ` · ${listing.installCount} install${\n listing.installCount === 1 ? '' : 's'\n }`\n : ''}\n </Typography>\n {/*\n Stars, as the mockup draws them (AGL-2173). The count\n stays alongside the average: \"5.0\" from one rating and\n from forty are not the same claim (AGL-655). An unrated\n listing says so rather than rendering nothing — silence\n read as \"no stars\", which is the opposite of \"not yet\n rated\".\n */}\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ alignItems: 'center' }}\n >\n {listing.ratingCount ? (\n <>\n <Rating\n size=\"small\"\n readOnly\n precision={0.1}\n value={Number(listing.ratingAverage ?? 0)}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${listing.ratingAverage ?? 0} (${listing.ratingCount})`}\n </Typography>\n </>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Not yet rated'}\n </Typography>\n )}\n </Stack>\n {listing.description ? (\n <Typography\n variant=\"body2\"\n color=\"text.secondary\"\n sx={{ flex: 1 }}\n // Four lines, then ellipsis (AGL-1002). Grid rows\n // stretch to their tallest card, so one publisher's\n // essay used to leave every card beside it mostly white\n // space; the full text is one click away.\n //\n // Plain `style`, not `sx`: emotion drops the\n // `-webkit-box` display value on the way through, and\n // without it the other three properties clamp nothing\n // (measured — `display` computed to `flow-root` while\n // line-clamp and box-orient came through fine). Nothing\n // here reads the theme, so there is nothing to lose.\n style={{\n display: '-webkit-box',\n WebkitBoxOrient: 'vertical',\n WebkitLineClamp: 4,\n overflow: 'hidden',\n }}\n >\n {listing.description}\n </Typography>\n ) : (\n <span style={{ flex: 1 }} />\n )}\n {/* Read-only state, then a link to the detail page — the\n only place an install happens (AGL-867). No install/buy\n action lives on the grid. */}\n {/* Install state reads as one statement (AGL-1002): the\n scope belongs with \"Installed\", not up in the chip row\n among the type and category pills, which say what the\n listing IS rather than what this workspace has done\n with it. Org-wide installs apply to every site\n (AGL-656). */}\n {isInstalled ? (\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={0.75}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"caption\" color=\"success.main\">\n {`Installed${\n installedVersion ? ` (v${installedVersion})` : ''\n }`}\n </Typography>\n {isPlugin && pluginState.scope === 'org' ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={\n pluginState.shadowed\n ? 'Org-wide (shadowed)'\n : 'Org-wide'\n }\n />\n ) : null}\n </Stack>\n ) : null}\n <AppLink\n componentVariant=\"button\"\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n href={detailHref ?? ''}\n disabled={!detailHref}\n >\n {priceUsd > 0 && !isInstalled\n ? `View details · $${priceUsd}`\n : 'View details'}\n </AppLink>\n </Stack>\n </Grid>\n )\n })}\n </Grid>\n {/* The query pages the shelf, so the total is not known — only\n whether another page exists. */}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={shown.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </>\n )}\n </CardDisplay>\n )\n}\nMarketplaceBrowse.displayName = 'MarketplaceBrowse'\n\nexport default MarketplaceBrowse\n"],"names":["mdiCheckDecagram","AppLink","CardDisplay","MdiIcon","ListPagination","ListQueryNotices","listQueryRefusals","useListGridFilter","Alert","Chip","Tooltip","Grid","MenuItem","Rating","Stack","TextField","Typography","collection","doc","getDoc","useListQuery","useEffect","useMemo","useRef","useState","buildRoute","PLATFORM_BRAND_NAME","pluginDocsHelp","Route","useConsoleHostRoute","useFirestore","useFirestoreCollection","useFirestoreDoc","useHostOrgId","useScopeTokens","ceilingedWindow","collectionCeiling","pluginRecordListQuery","pluginRecordsFromRows","LISTING_CATEGORIES","listingArtifactType","listingArtifactLabel","resolvePluginInstallState","BROWSE_SORTS","browseBase","MARKETPLACE_BROWSE_QUERY","ListingImage","INSTALL_STATE_CEILING","BROWSE_HEADERS","category","SEARCH_SETTLE_MS","MarketplaceBrowse","props","gridFilter","hostId","firestore","orgScoped","orgSlug","orgSlugProp","publisherId","handles","setHandles","viewerOrgId","setViewerOrgId","undefined","active","then","snapshot","get","catch","resolvedOrgSlug","subdomain","listingHref","listingId","ORG_MARKETPLACE_LISTING","publisherHref","profileId","ORG_MARKETPLACE_PUBLISHER","handle","selectFields","search","setSearch","searchWords","setSearchWords","timer","setTimeout","split","filter","Boolean","clearTimeout","sort","setSort","clauses","find","clause","field","value","listingsRef","rows","shown","status","shelfStatus","hasMore","page","setPage","pageSize","setPageSize","plan","declaration","request","base","deps","idField","confirmDisappearances","listings","filtering","length","refusals","refused","fields","headers","data","installedRead","installedDocs","truncated","installedTruncated","installed","map","definition","marketplace","deletedAt","orgId","hostPinRead","hostPinDocs","hostPinsTruncated","orgPinRead","orgPinDocs","orgPinsTruncated","tokens","scopeTokens","orgWide","viewerOrgWide","loaded","scopeLoaded","needsScope","datasetRead","memberScope","limit","datasetRows","datasetsTruncated","emailRead","emailDocs","emailsTruncated","hostDoc","artifactInstalls","note","version","id","String","next","seen","dataset","origin","facts","template","installedFrom","themeInstalledFrom","hostPins","pin","$id","orgPins","requestedHandles","Set","profileIds","listing","current","has","add","cancelled","Promise","all","entries","prev","Object","fromEntries","installStateTruncated","header","help","anchor","contentGutterX","contentGutterY","useFlexGap","direction","spacing","sx","mb","alignItems","flexWrap","rowGap","placeholder","onChange","event","target","size","minWidth","label","variant","color","onClick","setClauses","op","select","ml","severity","notices","container","componentInstall","artifactType","isPlugin","pluginState","latestVersion","artifactInstall","isInstalled","scope","installedVersion","priceUsd","Number","detailHref","xs","sm","md","border","borderColor","borderRadius","p","height","src","previewImageUrl","alt","displayName","width","objectFit","href","underline","reviewStatus","title","icon","path","fontSize","fontWeight","latestVersionReviewState","installCount","ratingCount","readOnly","precision","ratingAverage","description","flex","style","display","WebkitBoxOrient","WebkitLineClamp","overflow","span","shadowed","componentVariant","disabled","rowCount","onPageChange","onPageSizeChange"],"mappings":"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"}
@@ -153,6 +153,7 @@ import PublishPluginPage from "./publish-plugin-page.component.js";
153
153
  children: [
154
154
  /*#__PURE__*/ _jsx(MarketplacePaymentsNotice, {}),
155
155
  hostList.length > 1 ? /*#__PURE__*/ _jsxs(Stack, {
156
+ useFlexGap: true,
156
157
  direction: "row",
157
158
  spacing: 1,
158
159
  sx: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-hub.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { HubSections } from '@aglyn/shared-ui-next/components/hub-tabs'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport HostPluginsCard from './host-plugins-card.component'\nimport ListingDetailPage from './listing-detail-page.component'\nimport MarketplaceBrowse from './marketplace-browse.component'\nimport MarketplacePaymentsNotice from './marketplace-payments-notice.component'\nimport OrgLicencesPanel from './org-licences-panel.component'\nimport OrgPublishPanel from './org-publish-panel.component'\nimport OrgSellerPanel from './org-seller-panel.component'\nimport PublisherProfilePage from './publisher-profile-page.component'\nimport PublishPluginPage from './publish-plugin-page.component'\n\n/**\n * The Marketplace hub (AGL-772/2501), as a plugin surface (AGL-3080).\n *\n * ## What moved, and what did not\n *\n * Every section of this hub — browse, installed, licences, and the four\n * seller cards — was a hand-written console route under\n * `app/(app)/[orgSlug]/marketplace/`, ten route files whose whole job was to\n * assemble chrome around a body that already belonged to this plugin. The\n * shell's generic org plugin route does that assembly for every other\n * surface, from one declaration. So the declaration is now in `plugin.ts`\n * and this is the body.\n *\n * NO URL CHANGED. `ConsoleNavItem.ownsSubtree` is what makes that true: a\n * segment naming a declared section resolves as a section, and anything else\n * beneath the surface is handed over as `segments` for this page to read. So\n * `/{org}/marketplace/browse` is still the browse tab and\n * `/{org}/marketplace/{listingId}` is still a listing — the same addresses,\n * served by a declaration instead of by seventeen files.\n *\n * ## The gates are the shell's now, and that is the point\n *\n * The seller sections read the organization's REVENUE and its payout account,\n * and the old sections layout gated them itself — above the pages rather than\n * in the rail, because a route can be typed whether or not a tab was ever\n * offered. That is exactly what `ConsoleNavSection.permission` does, applied\n * by the same shell that hides the tab, so a hidden tab and a refused deep\n * link are one verdict instead of two copies of one. The permission map fails\n * OPEN while it loads, and the shell holds the whole page on it — so this\n * component never has to know that.\n *\n * Likewise `release_marketplace`: the sections layout wrapped itself in a\n * `FeatureGate` because the flag also feeds the plugin LOADER, and a hub that\n * went on rendering with its widgets empty made turning the feature off its\n * own broken state (AGL-2019). The shell applies the same flag through the\n * nav item's `navTabId`, around the same page.\n */\nexport function MarketplaceHub(props: ConsolePluginPageProps) {\n const {\n orgMount,\n basePath,\n sections,\n section,\n segments,\n permissions,\n viewerOrgs,\n org,\n } = props\n\n const orgId = orgMount?.orgId ?? null\n const orgSlug = orgMount?.orgSlug ?? ''\n const hostList = useMemo(\n () => (orgMount?.hosts ?? []).map((host) => ({ id: host.id, label: host.name })),\n [orgMount?.hosts],\n )\n /**\n * The site an install acts through. Installs pin to a site and pins are\n * validated against host membership, so every section that installs needs\n * the same answer — which is why it is chosen once here rather than per\n * section.\n */\n const [selectedHost, setSelectedHost] = useState('')\n const actingHost = selectedHost || hostList[0]?.id || ''\n\n const mount = basePath ?? ''\n\n if (!orgId) {\n return (\n <Alert severity=\"info\">\n {'Create your first site to start an organization, then browse and ' +\n 'install marketplace items here.'}\n </Alert>\n )\n }\n\n /*\n * An entity beneath the surface, not a section: `ownsSubtree` hands those\n * over with no `section` set. Each owns its own chrome, and none of them\n * takes the acting-site picker or the section rail — a listing is one\n * thing, not a tab of the hub.\n */\n if (!section && segments?.length) {\n if (segments[0] === 'publish' && segments[1] === 'plugin') {\n return (\n <PublishPluginPage\n orgId={orgId}\n basePath={mount}\n permissions={permissions}\n /*\n * The shell holds this whole page behind a spinner until the\n * permission map settles — it fails OPEN, so rendering on it early\n * offers a publish form to a member who is about to be refused.\n * Mounted at all therefore means settled, and saying so here keeps\n * the form's own gate honest when it is mounted anywhere else.\n */\n permissionsLoaded\n />\n )\n }\n if (segments[0] === 'publisher' && segments[1]) {\n return (\n <PublisherProfilePage\n segment={String(segments[1])}\n basePath={mount}\n actingHost={actingHost}\n permissions={permissions}\n orgSlug={orgSlug}\n />\n )\n }\n // Anything else one level down is a listing id. The surface owns its\n // subtree, so it also owns saying \"no such thing\" — which the detail page\n // has to be able to do anyway for a listing deleted while a link to it\n // was still in someone's inbox.\n if (segments.length === 1) {\n return (\n <ListingDetailPage\n listingId={String(segments[0])}\n orgId={orgId}\n basePath={mount}\n actingHost={actingHost}\n hosts={hostList}\n permissions={permissions}\n orgSlug={orgSlug}\n />\n )\n }\n return (\n <Alert severity=\"warning\">\n {'This marketplace page does not exist. It may have moved, or the ' +\n 'link may be incomplete.'}\n </Alert>\n )\n }\n\n if (!actingHost) {\n return (\n <Alert severity=\"info\">\n {'Add a site to your organization to install marketplace items — ' +\n 'installs apply to a site (or every site).'}\n </Alert>\n )\n }\n\n return (\n <Stack spacing={2}>\n {/*\n What this deployment cannot do, said BEFORE the click (AGL-2019). The\n app supplies only the fact, through the deployment-capability context\n its org layout provides; the sentence is this plugin's, because it\n names what still works without a Stripe platform. Draws nothing at all\n on a configured deployment.\n */}\n <MarketplacePaymentsNotice />\n {hostList.length > 1 ? (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Acting site'}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={actingHost}\n onChange={(event) => setSelectedHost(event.target.value)}\n sx={{ minWidth: 200 }}\n >\n {hostList.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n ) : null}\n <HubSections sections={sections ?? []}>\n {section === 'browse' ? (\n <MarketplaceBrowse\n hostId={actingHost}\n permissions={permissions}\n orgScoped\n // The shell already knows the org (AGL-867): passing it keeps\n // listing links resolving synchronously instead of via an async\n // hostIndex→org lookup that can come back empty and leave the\n // detail page unreachable from browse.\n orgSlug={orgSlug}\n />\n ) : section === 'installed' ? (\n <Stack spacing={2}>\n {/* A convenience, not the inventory (AGL-1011). Administering what\n you already run belongs in Plugins; this stays so uninstalling\n something you just installed does not send you elsewhere. */}\n <Alert\n severity=\"info\"\n action={\n <AppLink href={`/${orgSlug}/plugins`}>\n <Button size=\"small\" color=\"inherit\" component=\"span\">\n {'Open Plugins'}\n </Button>\n </AppLink>\n }\n >\n {'A quick list of what this organization installed from the ' +\n 'marketplace. Settings, per-site scope and built-in plugins ' +\n 'live in Plugins.'}\n </Alert>\n <HostPluginsCard hostId={actingHost} orgSlug={orgSlug} />\n </Stack>\n ) : section === 'licences' ? (\n <OrgLicencesPanel\n orgId={orgId}\n basePath={mount}\n viewerOrgs={viewerOrgs}\n />\n ) : section === 'upload' ? (\n <OrgPublishPanel\n orgId={orgId}\n hosts={hostList}\n basePath={mount}\n billingPath={orgMount?.billingPath}\n org={org as never}\n // The shell holds this page behind a spinner until the org doc\n // settles, so an org in hand means an org that arrived. Stated\n // rather than assumed: the fee this panel prints is a claim about\n // a publisher's plan, and the free rate is what an absent doc\n // resolves to.\n orgReady={Boolean(org)}\n />\n ) : section === 'profile' ||\n section === 'listings' ||\n section === 'payouts' ||\n section === 'sales' ? (\n <OrgSellerPanel orgId={orgId} section={section} basePath={mount} />\n ) : null}\n </HubSections>\n </Stack>\n )\n}\n\nMarketplaceHub.displayName = 'MarketplaceHub'\n\nexport default MarketplaceHub\n"],"names":["HubSections","AppLink","Alert","Button","MenuItem","Stack","TextField","Typography","useMemo","useState","HostPluginsCard","ListingDetailPage","MarketplaceBrowse","MarketplacePaymentsNotice","OrgLicencesPanel","OrgPublishPanel","OrgSellerPanel","PublisherProfilePage","PublishPluginPage","MarketplaceHub","props","hostList","orgMount","basePath","sections","section","segments","permissions","viewerOrgs","org","orgId","orgSlug","hosts","map","host","id","label","name","selectedHost","setSelectedHost","actingHost","mount","severity","length","permissionsLoaded","segment","String","listingId","spacing","direction","sx","alignItems","flexWrap","variant","color","select","size","value","onChange","event","target","minWidth","hostId","orgScoped","action","href","component","billingPath","orgReady","Boolean","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-hub.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { HubSections } from '@aglyn/shared-ui-next/components/hub-tabs'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport HostPluginsCard from './host-plugins-card.component'\nimport ListingDetailPage from './listing-detail-page.component'\nimport MarketplaceBrowse from './marketplace-browse.component'\nimport MarketplacePaymentsNotice from './marketplace-payments-notice.component'\nimport OrgLicencesPanel from './org-licences-panel.component'\nimport OrgPublishPanel from './org-publish-panel.component'\nimport OrgSellerPanel from './org-seller-panel.component'\nimport PublisherProfilePage from './publisher-profile-page.component'\nimport PublishPluginPage from './publish-plugin-page.component'\n\n/**\n * The Marketplace hub (AGL-772/2501), as a plugin surface (AGL-3080).\n *\n * ## What moved, and what did not\n *\n * Every section of this hub — browse, installed, licences, and the four\n * seller cards — was a hand-written console route under\n * `app/(app)/[orgSlug]/marketplace/`, ten route files whose whole job was to\n * assemble chrome around a body that already belonged to this plugin. The\n * shell's generic org plugin route does that assembly for every other\n * surface, from one declaration. So the declaration is now in `plugin.ts`\n * and this is the body.\n *\n * NO URL CHANGED. `ConsoleNavItem.ownsSubtree` is what makes that true: a\n * segment naming a declared section resolves as a section, and anything else\n * beneath the surface is handed over as `segments` for this page to read. So\n * `/{org}/marketplace/browse` is still the browse tab and\n * `/{org}/marketplace/{listingId}` is still a listing — the same addresses,\n * served by a declaration instead of by seventeen files.\n *\n * ## The gates are the shell's now, and that is the point\n *\n * The seller sections read the organization's REVENUE and its payout account,\n * and the old sections layout gated them itself — above the pages rather than\n * in the rail, because a route can be typed whether or not a tab was ever\n * offered. That is exactly what `ConsoleNavSection.permission` does, applied\n * by the same shell that hides the tab, so a hidden tab and a refused deep\n * link are one verdict instead of two copies of one. The permission map fails\n * OPEN while it loads, and the shell holds the whole page on it — so this\n * component never has to know that.\n *\n * Likewise `release_marketplace`: the sections layout wrapped itself in a\n * `FeatureGate` because the flag also feeds the plugin LOADER, and a hub that\n * went on rendering with its widgets empty made turning the feature off its\n * own broken state (AGL-2019). The shell applies the same flag through the\n * nav item's `navTabId`, around the same page.\n */\nexport function MarketplaceHub(props: ConsolePluginPageProps) {\n const {\n orgMount,\n basePath,\n sections,\n section,\n segments,\n permissions,\n viewerOrgs,\n org,\n } = props\n\n const orgId = orgMount?.orgId ?? null\n const orgSlug = orgMount?.orgSlug ?? ''\n const hostList = useMemo(\n () => (orgMount?.hosts ?? []).map((host) => ({ id: host.id, label: host.name })),\n [orgMount?.hosts],\n )\n /**\n * The site an install acts through. Installs pin to a site and pins are\n * validated against host membership, so every section that installs needs\n * the same answer — which is why it is chosen once here rather than per\n * section.\n */\n const [selectedHost, setSelectedHost] = useState('')\n const actingHost = selectedHost || hostList[0]?.id || ''\n\n const mount = basePath ?? ''\n\n if (!orgId) {\n return (\n <Alert severity=\"info\">\n {'Create your first site to start an organization, then browse and ' +\n 'install marketplace items here.'}\n </Alert>\n )\n }\n\n /*\n * An entity beneath the surface, not a section: `ownsSubtree` hands those\n * over with no `section` set. Each owns its own chrome, and none of them\n * takes the acting-site picker or the section rail — a listing is one\n * thing, not a tab of the hub.\n */\n if (!section && segments?.length) {\n if (segments[0] === 'publish' && segments[1] === 'plugin') {\n return (\n <PublishPluginPage\n orgId={orgId}\n basePath={mount}\n permissions={permissions}\n /*\n * The shell holds this whole page behind a spinner until the\n * permission map settles — it fails OPEN, so rendering on it early\n * offers a publish form to a member who is about to be refused.\n * Mounted at all therefore means settled, and saying so here keeps\n * the form's own gate honest when it is mounted anywhere else.\n */\n permissionsLoaded\n />\n )\n }\n if (segments[0] === 'publisher' && segments[1]) {\n return (\n <PublisherProfilePage\n segment={String(segments[1])}\n basePath={mount}\n actingHost={actingHost}\n permissions={permissions}\n orgSlug={orgSlug}\n />\n )\n }\n // Anything else one level down is a listing id. The surface owns its\n // subtree, so it also owns saying \"no such thing\" — which the detail page\n // has to be able to do anyway for a listing deleted while a link to it\n // was still in someone's inbox.\n if (segments.length === 1) {\n return (\n <ListingDetailPage\n listingId={String(segments[0])}\n orgId={orgId}\n basePath={mount}\n actingHost={actingHost}\n hosts={hostList}\n permissions={permissions}\n orgSlug={orgSlug}\n />\n )\n }\n return (\n <Alert severity=\"warning\">\n {'This marketplace page does not exist. It may have moved, or the ' +\n 'link may be incomplete.'}\n </Alert>\n )\n }\n\n if (!actingHost) {\n return (\n <Alert severity=\"info\">\n {'Add a site to your organization to install marketplace items — ' +\n 'installs apply to a site (or every site).'}\n </Alert>\n )\n }\n\n return (\n <Stack spacing={2}>\n {/*\n What this deployment cannot do, said BEFORE the click (AGL-2019). The\n app supplies only the fact, through the deployment-capability context\n its org layout provides; the sentence is this plugin's, because it\n names what still works without a Stripe platform. Draws nothing at all\n on a configured deployment.\n */}\n <MarketplacePaymentsNotice />\n {hostList.length > 1 ? (\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Acting site'}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={actingHost}\n onChange={(event) => setSelectedHost(event.target.value)}\n sx={{ minWidth: 200 }}\n >\n {hostList.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n ) : null}\n <HubSections sections={sections ?? []}>\n {section === 'browse' ? (\n <MarketplaceBrowse\n hostId={actingHost}\n permissions={permissions}\n orgScoped\n // The shell already knows the org (AGL-867): passing it keeps\n // listing links resolving synchronously instead of via an async\n // hostIndex→org lookup that can come back empty and leave the\n // detail page unreachable from browse.\n orgSlug={orgSlug}\n />\n ) : section === 'installed' ? (\n <Stack spacing={2}>\n {/* A convenience, not the inventory (AGL-1011). Administering what\n you already run belongs in Plugins; this stays so uninstalling\n something you just installed does not send you elsewhere. */}\n <Alert\n severity=\"info\"\n action={\n <AppLink href={`/${orgSlug}/plugins`}>\n <Button size=\"small\" color=\"inherit\" component=\"span\">\n {'Open Plugins'}\n </Button>\n </AppLink>\n }\n >\n {'A quick list of what this organization installed from the ' +\n 'marketplace. Settings, per-site scope and built-in plugins ' +\n 'live in Plugins.'}\n </Alert>\n <HostPluginsCard hostId={actingHost} orgSlug={orgSlug} />\n </Stack>\n ) : section === 'licences' ? (\n <OrgLicencesPanel\n orgId={orgId}\n basePath={mount}\n viewerOrgs={viewerOrgs}\n />\n ) : section === 'upload' ? (\n <OrgPublishPanel\n orgId={orgId}\n hosts={hostList}\n basePath={mount}\n billingPath={orgMount?.billingPath}\n org={org as never}\n // The shell holds this page behind a spinner until the org doc\n // settles, so an org in hand means an org that arrived. Stated\n // rather than assumed: the fee this panel prints is a claim about\n // a publisher's plan, and the free rate is what an absent doc\n // resolves to.\n orgReady={Boolean(org)}\n />\n ) : section === 'profile' ||\n section === 'listings' ||\n section === 'payouts' ||\n section === 'sales' ? (\n <OrgSellerPanel orgId={orgId} section={section} basePath={mount} />\n ) : null}\n </HubSections>\n </Stack>\n )\n}\n\nMarketplaceHub.displayName = 'MarketplaceHub'\n\nexport default MarketplaceHub\n"],"names":["HubSections","AppLink","Alert","Button","MenuItem","Stack","TextField","Typography","useMemo","useState","HostPluginsCard","ListingDetailPage","MarketplaceBrowse","MarketplacePaymentsNotice","OrgLicencesPanel","OrgPublishPanel","OrgSellerPanel","PublisherProfilePage","PublishPluginPage","MarketplaceHub","props","hostList","orgMount","basePath","sections","section","segments","permissions","viewerOrgs","org","orgId","orgSlug","hosts","map","host","id","label","name","selectedHost","setSelectedHost","actingHost","mount","severity","length","permissionsLoaded","segment","String","listingId","spacing","useFlexGap","direction","sx","alignItems","flexWrap","variant","color","select","size","value","onChange","event","target","minWidth","hostId","orgScoped","action","href","component","billingPath","orgReady","Boolean","displayName"],"mappings":"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"}
@@ -230,6 +230,7 @@ const when = (ms)=>ms ? new Date(ms).toLocaleString() : '—';
230
230
  spacing: 1,
231
231
  children: [
232
232
  /*#__PURE__*/ _jsxs(Stack, {
233
+ useFlexGap: true,
233
234
  direction: "row",
234
235
  sx: {
235
236
  flexWrap: 'wrap',
@@ -275,6 +276,7 @@ const when = (ms)=>ms ? new Date(ms).toLocaleString() : '—';
275
276
  children: report.resolution
276
277
  }) : null,
277
278
  /*#__PURE__*/ _jsxs(Stack, {
279
+ useFlexGap: true,
278
280
  direction: "row",
279
281
  sx: {
280
282
  flexWrap: 'wrap',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-reports-page.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { ICON_VARIANT_SYMBOL_FLAG } from '@aglyn/shared-data-enums'\nimport { PageHeaderHelp, pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n LinearProgress,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\ninterface MarketplaceReportRow {\n id: string\n status: string\n targetType: string\n listingId: string | null\n listingName: string | null\n publisherOrgId: string | null\n reviewUid: string | null\n reason: string | null\n reporterKnown: boolean\n reporterUid: string | null\n resolution: string | null\n resolvedByEmail: string | null\n createdAtMs: number | null\n updatedAtMs: number | null\n}\n\nconst STATUS_COLOR: Record<string, 'warning' | 'info' | 'success' | 'default'> =\n {\n open: 'warning',\n reviewing: 'info',\n actioned: 'success',\n dismissed: 'default',\n }\n\nconst when = (ms: number | null) =>\n ms ? new Date(ms).toLocaleString() : '—'\n\n/**\n * The marketplace report queue (AGL-2310).\n *\n * The report button on a listing has told users their report was filed since\n * it shipped. `marketplaceReports` was written and read by NOTHING — no\n * surface, no cron, no collection group — so every report was stored,\n * acknowledged and unreachable, and `status` never moved off `open` because\n * no queue displayed it.\n *\n * The page is deliberately the `abuseReports` queue in miniature: a list, a\n * status transition, a note required to close, and an `adminAudit` row per\n * transition. Two vocabularies for the same act is how one of them stops\n * being maintained.\n *\n * The REASON is the row. Everything else — the listing, the publisher, the\n * date — is context for it, which is why it is rendered in full rather than\n * truncated into a column, and why it is never redacted by staff tier.\n */\nexport function MarketplaceReportsPage() {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [reports, setReports] = useState<MarketplaceReportRow[] | null>(null)\n const [identityVisible, setIdentityVisible] = useState(false)\n /** The status holds more reports than the route read (AGL-3321). */\n const [more, setMore] = useState(false)\n const [statusFilter, setStatusFilter] = useState('open')\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(false)\n const [busyId, setBusyId] = useState<string | null>(null)\n const [notes, setNotes] = useState<Record<string, string>>({})\n const [epoch, setEpoch] = useState(0)\n\n useEffect(() => {\n let active = true\n void (async () => {\n /*\n * The staff claim is the SHELL's now (AGL-3080). This page used to\n * hold `isStaff !== true` here, because it owned its own route and a\n * request fired from the claim's loading `null` answers a question\n * nobody asked. The generic staff route renders every plugin page\n * inside `StaffOnly`, which renders NOTHING while the claim resolves\n * and 404s a non-holder — so being mounted at all is the claim, and a\n * second check here would be a copy free to disagree with it.\n */\n if (!user) return\n setLoading(true)\n setError(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/marketplace/admin/reports?status=${encodeURIComponent(statusFilter)}`,\n )\n const body = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(body?.error ?? 'Report lookup failed')\n } else {\n setReports((body?.reports ?? []) as MarketplaceReportRow[])\n setMore(Boolean(body?.more))\n setIdentityVisible(Boolean(body?.identityVisible))\n }\n } catch {\n if (active) setError('Report lookup failed')\n } finally {\n if (active) setLoading(false)\n }\n })()\n return () => {\n active = false\n }\n }, [user, statusFilter, epoch])\n\n const transition = useCallback(\n async (report: MarketplaceReportRow, status: string) => {\n setBusyId(report.id)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/admin/reports',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: report.id,\n status,\n resolution: notes[report.id] ?? '',\n }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // The server's own sentence, not a generic failure: the one refusal\n // this route issues (\"say what you did\") is instruction, and\n // replacing it with \"Update failed\" would leave the reviewer\n // guessing at a rule the server already stated.\n enqueueSnackbar(payload?.error ?? 'Update failed', {\n variant: 'warning',\n allowDuplicate: true,\n })\n return\n }\n enqueueSnackbar(`Marked ${status}`, { variant: 'success', persist: false })\n setEpoch((value) => value + 1)\n } catch {\n enqueueSnackbar('An error has occurred', { variant: 'error' })\n } finally {\n setBusyId(null)\n }\n },\n [user, notes, enqueueSnackbar],\n )\n\n return (\n <>\n {/*\n The abuse-queue topic, deliberately: this is the same triage job on a\n different surface, and a second docs page describing the same four\n statuses is a second page to forget to update.\n\n ANCHORED, though, and published rather than declared. The abuse\n queue's own page opens the same topic bare, and two help icons landing\n in exactly the same place are interchangeable — which is the thing\n `docs-help-destinations` exists to catch. A `staffPages` entry carries\n one `docsTopic` and no anchor, so `#statuses` — the four states and\n what closing one means, which is what a reader arriving from HERE\n needs — comes up through the header seam instead.\n */}\n <PageHeaderHelp topic=\"abuseReports\" anchor=\"#statuses\" />\n <Stack spacing={3}>\n {loading && <LinearProgress />}\n {error && <Alert severity=\"error\">{error}</Alert>}\n <CardDisplay\n header={'Reported listings and reviews'}\n help={pluginDocsHelp('abuseReports', {\n excerpt:\n 'Reports users filed against a marketplace listing or a ' +\n 'review, with the reason each one gave. Closing one needs ' +\n 'a note, and every change is audited.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <TextField\n select\n size=\"small\"\n value={statusFilter}\n onChange={(event) => {\n setReports(null)\n setStatusFilter(event.target.value)\n }}\n >\n <MenuItem value=\"open\">{'Open'}</MenuItem>\n <MenuItem value=\"reviewing\">{'Reviewing'}</MenuItem>\n <MenuItem value=\"actioned\">{'Actioned'}</MenuItem>\n <MenuItem value=\"dismissed\">{'Dismissed'}</MenuItem>\n <MenuItem value=\"\">{'Everything'}</MenuItem>\n </TextField>\n ),\n }}\n >\n {more ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {'These are the most recently updated reports in this ' +\n 'state, and there are older ones not shown. Moving a ' +\n 'report on takes it out of this list.'}\n </Alert>\n ) : null}\n {!identityVisible ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {'Reporter accounts are hidden at your access level. Every ' +\n 'report still shows whether there is somebody behind it.'}\n </Alert>\n ) : null}\n {reports === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading ? 'Reading reports…' : ''}\n </Typography>\n ) : reports.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Nothing in this state.'}\n </Typography>\n ) : (\n <Stack spacing={3}>\n {reports.map((report) => (\n <Stack key={report.id} spacing={1}>\n <Stack\n direction=\"row\"\n sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}\n >\n <Chip\n size=\"small\"\n color={STATUS_COLOR[report.status] ?? 'default'}\n label={report.status}\n />\n <Chip size=\"small\" variant=\"outlined\" label={report.targetType} />\n <Typography variant=\"subtitle2\">\n {report.listingName || report.listingId || report.id}\n </Typography>\n </Stack>\n {/* THE REASON, in full. This is the whole content of a\n report; a queue that summarised it would be the\n original silence with an extra click in it. */}\n <Typography variant=\"body2\">\n {report.reason || '(no reason given)'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {[\n report.publisherOrgId\n ? `publisher ${report.publisherOrgId}`\n : null,\n // A REDACTED reporter and an ANONYMOUS one are\n // different facts: only the second means there is\n // nobody to follow up with.\n report.reporterUid\n ? `reporter ${report.reporterUid}`\n : report.reporterKnown\n ? 'reporter hidden'\n : 'no reporter recorded',\n `filed ${when(report.createdAtMs)}`,\n report.resolvedByEmail\n ? `closed by ${report.resolvedByEmail}`\n : null,\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n {report.resolution ? (\n <Alert severity=\"success\">{report.resolution}</Alert>\n ) : null}\n <Stack\n direction=\"row\"\n sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}\n >\n <TextField\n size=\"small\"\n placeholder=\"What did you do, and why?\"\n value={notes[report.id] ?? ''}\n onChange={(event) =>\n setNotes((previous) => ({\n ...previous,\n [report.id]: event.target.value,\n }))\n }\n sx={{ flex: 1, minWidth: 260 }}\n />\n <Button\n size=\"small\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'reviewing')}\n >\n {'Reviewing'}\n </Button>\n <Button\n size=\"small\"\n color=\"success\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'actioned')}\n >\n {'Actioned'}\n </Button>\n <Button\n size=\"small\"\n color=\"warning\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'dismissed')}\n >\n {'Dismiss'}\n </Button>\n </Stack>\n </Stack>\n ))}\n </Stack>\n )}\n </CardDisplay>\n </Stack>\n </>\n )\n}\nMarketplaceReportsPage.displayName = 'MarketplaceReportsPage'\n\nexport default MarketplaceReportsPage\n"],"names":["PageHeaderHelp","pluginDocsHelp","CardDisplay","useSnackbar","Alert","Button","Chip","LinearProgress","MenuItem","Stack","TextField","Typography","useCallback","useEffect","useState","useUser","authorizedFetch","STATUS_COLOR","open","reviewing","actioned","dismissed","when","ms","Date","toLocaleString","MarketplaceReportsPage","data","user","enqueueSnackbar","reports","setReports","identityVisible","setIdentityVisible","more","setMore","statusFilter","setStatusFilter","error","setError","loading","setLoading","busyId","setBusyId","notes","setNotes","epoch","setEpoch","active","response","encodeURIComponent","body","json","catch","ok","Boolean","transition","report","status","id","method","headers","JSON","stringify","resolution","payload","variant","allowDuplicate","persist","value","topic","anchor","spacing","severity","header","help","excerpt","contentGutterX","contentGutterY","HeaderProps","action","select","size","onChange","event","target","sx","mb","color","length","map","direction","flexWrap","gap","alignItems","label","targetType","listingName","listingId","reason","publisherOrgId","reporterUid","reporterKnown","createdAtMs","resolvedByEmail","filter","join","placeholder","previous","flex","minWidth","disabled","onClick","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-reports-page.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { ICON_VARIANT_SYMBOL_FLAG } from '@aglyn/shared-data-enums'\nimport { PageHeaderHelp, pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n LinearProgress,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\ninterface MarketplaceReportRow {\n id: string\n status: string\n targetType: string\n listingId: string | null\n listingName: string | null\n publisherOrgId: string | null\n reviewUid: string | null\n reason: string | null\n reporterKnown: boolean\n reporterUid: string | null\n resolution: string | null\n resolvedByEmail: string | null\n createdAtMs: number | null\n updatedAtMs: number | null\n}\n\nconst STATUS_COLOR: Record<string, 'warning' | 'info' | 'success' | 'default'> =\n {\n open: 'warning',\n reviewing: 'info',\n actioned: 'success',\n dismissed: 'default',\n }\n\nconst when = (ms: number | null) =>\n ms ? new Date(ms).toLocaleString() : '—'\n\n/**\n * The marketplace report queue (AGL-2310).\n *\n * The report button on a listing has told users their report was filed since\n * it shipped. `marketplaceReports` was written and read by NOTHING — no\n * surface, no cron, no collection group — so every report was stored,\n * acknowledged and unreachable, and `status` never moved off `open` because\n * no queue displayed it.\n *\n * The page is deliberately the `abuseReports` queue in miniature: a list, a\n * status transition, a note required to close, and an `adminAudit` row per\n * transition. Two vocabularies for the same act is how one of them stops\n * being maintained.\n *\n * The REASON is the row. Everything else — the listing, the publisher, the\n * date — is context for it, which is why it is rendered in full rather than\n * truncated into a column, and why it is never redacted by staff tier.\n */\nexport function MarketplaceReportsPage() {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [reports, setReports] = useState<MarketplaceReportRow[] | null>(null)\n const [identityVisible, setIdentityVisible] = useState(false)\n /** The status holds more reports than the route read (AGL-3321). */\n const [more, setMore] = useState(false)\n const [statusFilter, setStatusFilter] = useState('open')\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(false)\n const [busyId, setBusyId] = useState<string | null>(null)\n const [notes, setNotes] = useState<Record<string, string>>({})\n const [epoch, setEpoch] = useState(0)\n\n useEffect(() => {\n let active = true\n void (async () => {\n /*\n * The staff claim is the SHELL's now (AGL-3080). This page used to\n * hold `isStaff !== true` here, because it owned its own route and a\n * request fired from the claim's loading `null` answers a question\n * nobody asked. The generic staff route renders every plugin page\n * inside `StaffOnly`, which renders NOTHING while the claim resolves\n * and 404s a non-holder — so being mounted at all is the claim, and a\n * second check here would be a copy free to disagree with it.\n */\n if (!user) return\n setLoading(true)\n setError(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/marketplace/admin/reports?status=${encodeURIComponent(statusFilter)}`,\n )\n const body = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(body?.error ?? 'Report lookup failed')\n } else {\n setReports((body?.reports ?? []) as MarketplaceReportRow[])\n setMore(Boolean(body?.more))\n setIdentityVisible(Boolean(body?.identityVisible))\n }\n } catch {\n if (active) setError('Report lookup failed')\n } finally {\n if (active) setLoading(false)\n }\n })()\n return () => {\n active = false\n }\n }, [user, statusFilter, epoch])\n\n const transition = useCallback(\n async (report: MarketplaceReportRow, status: string) => {\n setBusyId(report.id)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/admin/reports',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: report.id,\n status,\n resolution: notes[report.id] ?? '',\n }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // The server's own sentence, not a generic failure: the one refusal\n // this route issues (\"say what you did\") is instruction, and\n // replacing it with \"Update failed\" would leave the reviewer\n // guessing at a rule the server already stated.\n enqueueSnackbar(payload?.error ?? 'Update failed', {\n variant: 'warning',\n allowDuplicate: true,\n })\n return\n }\n enqueueSnackbar(`Marked ${status}`, { variant: 'success', persist: false })\n setEpoch((value) => value + 1)\n } catch {\n enqueueSnackbar('An error has occurred', { variant: 'error' })\n } finally {\n setBusyId(null)\n }\n },\n [user, notes, enqueueSnackbar],\n )\n\n return (\n <>\n {/*\n The abuse-queue topic, deliberately: this is the same triage job on a\n different surface, and a second docs page describing the same four\n statuses is a second page to forget to update.\n\n ANCHORED, though, and published rather than declared. The abuse\n queue's own page opens the same topic bare, and two help icons landing\n in exactly the same place are interchangeable — which is the thing\n `docs-help-destinations` exists to catch. A `staffPages` entry carries\n one `docsTopic` and no anchor, so `#statuses` — the four states and\n what closing one means, which is what a reader arriving from HERE\n needs — comes up through the header seam instead.\n */}\n <PageHeaderHelp topic=\"abuseReports\" anchor=\"#statuses\" />\n <Stack spacing={3}>\n {loading && <LinearProgress />}\n {error && <Alert severity=\"error\">{error}</Alert>}\n <CardDisplay\n header={'Reported listings and reviews'}\n help={pluginDocsHelp('abuseReports', {\n excerpt:\n 'Reports users filed against a marketplace listing or a ' +\n 'review, with the reason each one gave. Closing one needs ' +\n 'a note, and every change is audited.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <TextField\n select\n size=\"small\"\n value={statusFilter}\n onChange={(event) => {\n setReports(null)\n setStatusFilter(event.target.value)\n }}\n >\n <MenuItem value=\"open\">{'Open'}</MenuItem>\n <MenuItem value=\"reviewing\">{'Reviewing'}</MenuItem>\n <MenuItem value=\"actioned\">{'Actioned'}</MenuItem>\n <MenuItem value=\"dismissed\">{'Dismissed'}</MenuItem>\n <MenuItem value=\"\">{'Everything'}</MenuItem>\n </TextField>\n ),\n }}\n >\n {more ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {'These are the most recently updated reports in this ' +\n 'state, and there are older ones not shown. Moving a ' +\n 'report on takes it out of this list.'}\n </Alert>\n ) : null}\n {!identityVisible ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {'Reporter accounts are hidden at your access level. Every ' +\n 'report still shows whether there is somebody behind it.'}\n </Alert>\n ) : null}\n {reports === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading ? 'Reading reports…' : ''}\n </Typography>\n ) : reports.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Nothing in this state.'}\n </Typography>\n ) : (\n <Stack spacing={3}>\n {reports.map((report) => (\n <Stack key={report.id} spacing={1}>\n <Stack\n useFlexGap\n direction=\"row\"\n sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}\n >\n <Chip\n size=\"small\"\n color={STATUS_COLOR[report.status] ?? 'default'}\n label={report.status}\n />\n <Chip size=\"small\" variant=\"outlined\" label={report.targetType} />\n <Typography variant=\"subtitle2\">\n {report.listingName || report.listingId || report.id}\n </Typography>\n </Stack>\n {/* THE REASON, in full. This is the whole content of a\n report; a queue that summarised it would be the\n original silence with an extra click in it. */}\n <Typography variant=\"body2\">\n {report.reason || '(no reason given)'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {[\n report.publisherOrgId\n ? `publisher ${report.publisherOrgId}`\n : null,\n // A REDACTED reporter and an ANONYMOUS one are\n // different facts: only the second means there is\n // nobody to follow up with.\n report.reporterUid\n ? `reporter ${report.reporterUid}`\n : report.reporterKnown\n ? 'reporter hidden'\n : 'no reporter recorded',\n `filed ${when(report.createdAtMs)}`,\n report.resolvedByEmail\n ? `closed by ${report.resolvedByEmail}`\n : null,\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n {report.resolution ? (\n <Alert severity=\"success\">{report.resolution}</Alert>\n ) : null}\n <Stack\n useFlexGap\n direction=\"row\"\n sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}\n >\n <TextField\n size=\"small\"\n placeholder=\"What did you do, and why?\"\n value={notes[report.id] ?? ''}\n onChange={(event) =>\n setNotes((previous) => ({\n ...previous,\n [report.id]: event.target.value,\n }))\n }\n sx={{ flex: 1, minWidth: 260 }}\n />\n <Button\n size=\"small\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'reviewing')}\n >\n {'Reviewing'}\n </Button>\n <Button\n size=\"small\"\n color=\"success\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'actioned')}\n >\n {'Actioned'}\n </Button>\n <Button\n size=\"small\"\n color=\"warning\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'dismissed')}\n >\n {'Dismiss'}\n </Button>\n </Stack>\n </Stack>\n ))}\n </Stack>\n )}\n </CardDisplay>\n </Stack>\n </>\n )\n}\nMarketplaceReportsPage.displayName = 'MarketplaceReportsPage'\n\nexport default MarketplaceReportsPage\n"],"names":["PageHeaderHelp","pluginDocsHelp","CardDisplay","useSnackbar","Alert","Button","Chip","LinearProgress","MenuItem","Stack","TextField","Typography","useCallback","useEffect","useState","useUser","authorizedFetch","STATUS_COLOR","open","reviewing","actioned","dismissed","when","ms","Date","toLocaleString","MarketplaceReportsPage","data","user","enqueueSnackbar","reports","setReports","identityVisible","setIdentityVisible","more","setMore","statusFilter","setStatusFilter","error","setError","loading","setLoading","busyId","setBusyId","notes","setNotes","epoch","setEpoch","active","response","encodeURIComponent","body","json","catch","ok","Boolean","transition","report","status","id","method","headers","JSON","stringify","resolution","payload","variant","allowDuplicate","persist","value","topic","anchor","spacing","severity","header","help","excerpt","contentGutterX","contentGutterY","HeaderProps","action","select","size","onChange","event","target","sx","mb","color","length","map","useFlexGap","direction","flexWrap","gap","alignItems","label","targetType","listingName","listingId","reason","publisherOrgId","reporterUid","reporterKnown","createdAtMs","resolvedByEmail","filter","join","placeholder","previous","flex","minWidth","disabled","onClick","displayName"],"mappings":"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"}