@aglyn/plugins-marketplace 1.0.0 → 1.2.0

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 (48) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/listing-detail-editor.component.js +26 -26
  3. package/src/lib/components/listing-detail-editor.component.js.map +1 -1
  4. package/src/lib/components/plugin-review-detail.component.js +17 -3
  5. package/src/lib/components/plugin-review-detail.component.js.map +1 -1
  6. package/src/lib/components/plugin-reviews-queue.component.js +88 -12
  7. package/src/lib/components/plugin-reviews-queue.component.js.map +1 -1
  8. package/src/lib/components/site-starter-review-detail.component.d.ts +115 -0
  9. package/src/lib/components/site-starter-review-detail.component.js +785 -0
  10. package/src/lib/components/site-starter-review-detail.component.js.map +1 -0
  11. package/src/lib/components/starter-picker-shelf.component.d.ts +7 -0
  12. package/src/lib/components/starter-picker-shelf.component.js +75 -1
  13. package/src/lib/components/starter-picker-shelf.component.js.map +1 -1
  14. package/src/lib/hooks/use-starter-answers.d.ts +41 -0
  15. package/src/lib/hooks/use-starter-answers.js +75 -0
  16. package/src/lib/hooks/use-starter-answers.js.map +1 -0
  17. package/src/lib/model/listing-query.d.ts +20 -2
  18. package/src/lib/model/listing-query.js +55 -0
  19. package/src/lib/model/listing-query.js.map +1 -1
  20. package/src/lib/model/plugin-rejection-categories.d.ts +17 -2
  21. package/src/lib/model/plugin-rejection-categories.js +48 -4
  22. package/src/lib/model/plugin-rejection-categories.js.map +1 -1
  23. package/src/lib/model/plugin-review-checklist.d.ts +38 -8
  24. package/src/lib/model/plugin-review-checklist.js +60 -11
  25. package/src/lib/model/plugin-review-checklist.js.map +1 -1
  26. package/src/lib/model/plugin-review-status.d.ts +8 -0
  27. package/src/lib/model/plugin-review-status.js +34 -0
  28. package/src/lib/model/plugin-review-status.js.map +1 -1
  29. package/src/lib/model/starter-category.d.ts +58 -0
  30. package/src/lib/model/starter-category.js +79 -0
  31. package/src/lib/model/starter-category.js.map +1 -0
  32. package/src/lib/model/starter-query.d.ts +27 -0
  33. package/src/lib/model/starter-query.js +42 -0
  34. package/src/lib/model/starter-query.js.map +1 -1
  35. package/src/lib/server/admin-reviews.js +294 -45
  36. package/src/lib/server/admin-reviews.js.map +1 -1
  37. package/src/lib/server/publish-plugin.js +46 -4
  38. package/src/lib/server/publish-plugin.js.map +1 -1
  39. package/src/lib/server/publish-starter.js +4 -0
  40. package/src/lib/server/publish-starter.js.map +1 -1
  41. package/src/lib/server/publisher-review-email.d.ts +5 -0
  42. package/src/lib/server/publisher-review-email.js +2 -1
  43. package/src/lib/server/publisher-review-email.js.map +1 -1
  44. package/src/lib/server/starters.d.ts +4 -1
  45. package/src/lib/server/starters.js +128 -28
  46. package/src/lib/server/starters.js.map +1 -1
  47. package/src/lib/server.js +12 -1
  48. package/src/lib/server.js.map +1 -1
@@ -25,7 +25,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
25
25
  import { useUser } from "@aglyn/tenant-feature-instance";
26
26
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
27
27
  import { pluginDocsHelp } from "@aglyn/aglyn";
28
- import { reviewStatusMeaning } from "../model/plugin-review-status.js";
28
+ import { reviewStatusMeaning, starterReviewStatusMeaning } from "../model/plugin-review-status.js";
29
29
  import { REVIEW_QUEUE_QUERY } from "../model/listing-query.js";
30
30
  const STATUS_FILTERS = [
31
31
  {
@@ -48,6 +48,12 @@ const STATUS_FILTERS = [
48
48
  value: 'verified',
49
49
  label: 'Verified'
50
50
  },
51
+ // A rejected site starter stays rejected until its publisher publishes it
52
+ // again (AGL-4049), so the Starters section needs a way to find it.
53
+ {
54
+ value: 'rejected',
55
+ label: 'Rejected'
56
+ },
51
57
  {
52
58
  value: 'hidden',
53
59
  label: 'Taken down'
@@ -65,13 +71,16 @@ const QUEUE_OPTIONS = {
65
71
  const NO_MORE = {
66
72
  queue: false,
67
73
  listed: false,
68
- verification: false
74
+ verification: false,
75
+ starters: false
69
76
  };
70
77
  const FIRST_PAGES = {
71
78
  queue: 0,
72
79
  listed: 0,
73
- verification: 0
80
+ verification: 0,
81
+ starters: 0
74
82
  };
83
+ const plural = (count, one)=>`${count} ${one}${count === 1 ? '' : 's'}`;
75
84
  /**
76
85
  * Staff marketplace review index (AGL-961).
77
86
  *
@@ -94,6 +103,7 @@ const FIRST_PAGES = {
94
103
  const [queue, setQueue] = useState([]);
95
104
  const [listed, setListed] = useState([]);
96
105
  const [verificationRequests, setVerificationRequests] = useState([]);
106
+ const [starters, setStarters] = useState([]);
97
107
  const [publishers, setPublishers] = useState({});
98
108
  const [loaded, setLoaded] = useState(false);
99
109
  const [loadError, setLoadError] = useState(null);
@@ -128,20 +138,21 @@ const FIRST_PAGES = {
128
138
  const response = await authorizedFetch(user, `/api/marketplace/admin/reviews${suffix ? `?${suffix}` : ''}`);
129
139
  const payload = await response.json().catch(()=>({}));
130
140
  if (response.ok) {
131
- var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6;
141
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
132
142
  setLoadError(null);
133
143
  setQueue((_ref = payload == null ? void 0 : payload.queue) != null ? _ref : []);
134
144
  setListed((_ref1 = payload == null ? void 0 : payload.listed) != null ? _ref1 : []);
135
145
  setVerificationRequests((_ref2 = payload == null ? void 0 : payload.verificationRequests) != null ? _ref2 : []);
136
- setPublishers((_ref3 = payload == null ? void 0 : payload.publishers) != null ? _ref3 : {});
137
- setMore(_extends({}, NO_MORE, (_ref4 = payload == null ? void 0 : payload.more) != null ? _ref4 : {}));
138
- setRefused((_ref5 = payload == null ? void 0 : payload.refused) != null ? _ref5 : []);
139
- setNotices((_ref6 = payload == null ? void 0 : payload.notices) != null ? _ref6 : []);
146
+ setStarters((_ref3 = payload == null ? void 0 : payload.starters) != null ? _ref3 : []);
147
+ setPublishers((_ref4 = payload == null ? void 0 : payload.publishers) != null ? _ref4 : {});
148
+ setMore(_extends({}, NO_MORE, (_ref5 = payload == null ? void 0 : payload.more) != null ? _ref5 : {}));
149
+ setRefused((_ref6 = payload == null ? void 0 : payload.refused) != null ? _ref6 : []);
150
+ setNotices((_ref7 = payload == null ? void 0 : payload.notices) != null ? _ref7 : []);
140
151
  } else {
141
- var _ref7;
152
+ var _ref8;
142
153
  // Named, not swallowed. An empty queue and a queue that could not be
143
154
  // read look identical, and only one of them means there is no work.
144
- setLoadError((_ref7 = payload == null ? void 0 : payload.error) != null ? _ref7 : `Loading the queue failed (${response.status})`);
155
+ setLoadError((_ref8 = payload == null ? void 0 : payload.error) != null ? _ref8 : `Loading the queue failed (${response.status})`);
145
156
  }
146
157
  setLoaded(true);
147
158
  }, [
@@ -168,10 +179,12 @@ const FIRST_PAGES = {
168
179
  const visibleQueue = pageOf('queue', queue);
169
180
  const visibleListed = pageOf('listed', listed);
170
181
  const visibleVerification = pageOf('verification', verificationRequests);
182
+ const visibleStarters = pageOf('starters', starters);
171
183
  const total = {
172
184
  queue: queue.length,
173
185
  listed: listed.length,
174
- verification: verificationRequests.length
186
+ verification: verificationRequests.length,
187
+ starters: starters.length
175
188
  };
176
189
  const filtering = asked.length > 0 || status !== 'all';
177
190
  const refusals = useMemo(()=>listQueryRefusals(refused, {
@@ -251,7 +264,7 @@ const FIRST_PAGES = {
251
264
  children: [
252
265
  /*#__PURE__*/ _jsx(TextField, {
253
266
  size: "small",
254
- placeholder: "Search plugin names",
267
+ placeholder: "Search plugin and starter names",
255
268
  value: search,
256
269
  onChange: (event)=>setSearch(event.target.value),
257
270
  sx: {
@@ -389,6 +402,69 @@ const FIRST_PAGES = {
389
402
  })
390
403
  ]
391
404
  }),
405
+ /*#__PURE__*/ _jsxs(CardDisplay, {
406
+ header: `${status === 'all' ? 'Site starters awaiting review' : 'Site starters'} (${total.starters}${more.starters ? '+' : ''})`,
407
+ help: pluginDocsHelp('staffConsole', {
408
+ anchor: '#plugin-reviews-site-starters'
409
+ }),
410
+ contentGutterX: true,
411
+ contentGutterY: true,
412
+ children: [
413
+ /*#__PURE__*/ _jsx(Typography, {
414
+ variant: "caption",
415
+ color: "text.secondary",
416
+ sx: {
417
+ display: 'block',
418
+ mb: 1
419
+ },
420
+ children: 'Nobody but its publisher can create a site from a ' + 'starter until it is listed. Pick a status above to see ' + 'listed, rejected or taken-down starters.'
421
+ }),
422
+ visibleStarters.length ? /*#__PURE__*/ _jsxs(Stack, {
423
+ children: [
424
+ visibleStarters.map((entry)=>{
425
+ const meaning = starterReviewStatusMeaning(entry.reviewStatus);
426
+ return row(`starter-${entry.listingId}`, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
427
+ children: [
428
+ /*#__PURE__*/ _jsx(Chip, {
429
+ size: "small",
430
+ color: meaning.color,
431
+ label: meaning.label
432
+ }),
433
+ /*#__PURE__*/ _jsx(Chip, {
434
+ size: "small",
435
+ variant: "outlined",
436
+ label: `v${entry.version || '—'}`
437
+ }),
438
+ entry.reviewedVersion && entry.reviewedVersion !== entry.version ? /*#__PURE__*/ _jsx(Chip, {
439
+ size: "small",
440
+ color: "warning",
441
+ label: `v${entry.version} since review (v${entry.reviewedVersion})`
442
+ }) : null,
443
+ entry.official ? /*#__PURE__*/ _jsx(Chip, {
444
+ size: "small",
445
+ variant: "outlined",
446
+ label: "Official"
447
+ }) : null,
448
+ entry.hidden ? /*#__PURE__*/ _jsx(Chip, {
449
+ size: "small",
450
+ color: "error",
451
+ label: "Taken down"
452
+ }) : null
453
+ ]
454
+ }), [
455
+ plural(entry.pages, 'page'),
456
+ entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free',
457
+ entry.submittedAt ? `Submitted ${new Date(entry.submittedAt).toLocaleDateString()}` : ''
458
+ ].filter(Boolean).join(' · '));
459
+ }),
460
+ pager('starters', visibleStarters.length)
461
+ ]
462
+ }) : /*#__PURE__*/ _jsx(Alert, {
463
+ severity: filtering ? 'info' : 'success',
464
+ children: filtering ? 'No site starters match this filter.' : 'No site starters waiting for review.'
465
+ })
466
+ ]
467
+ }),
392
468
  /*#__PURE__*/ _jsxs(CardDisplay, {
393
469
  header: `Listed plugins (${total.listed}${more.listed ? '+' : ''})`,
394
470
  help: pluginDocsHelp('manifestAndEnvs', {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-reviews-queue.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 { PageHeaderHelp } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } 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 type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n Alert,\n Chip,\n MenuItem,\n Skeleton,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { reviewStatusMeaning } from '../model/plugin-review-status'\nimport {\n REVIEW_QUEUE_QUERY,\n type ReviewQueueSection,\n} from '../model/listing-query'\n\ninterface QueueRow {\n listingId: string\n displayName: string\n description: string\n license: string\n categories: string[]\n profileId: string\n reviewStatus: string\n priceUsd: number\n version: string\n hidden: boolean\n /** Private plugin (AGL-968): reviewed identically, never listed. */\n private: boolean\n /** The publisher's standing ask for the Verified badge (AGL-1217). */\n verificationRequest?: { state?: string; requestedAt?: unknown } | null\n}\n\ninterface ListedRow {\n listingId: string\n displayName: string\n reviewStatus: string\n profileId: string\n latestVersion: string\n hidden: boolean\n hiddenReason: string\n realmVersions: number\n versionCount: number\n private: boolean\n}\n\nconst STATUS_FILTERS = [\n { value: 'all', label: 'All statuses' },\n { value: 'submitted', label: 'Submitted' },\n { value: 'in_review', label: 'In review' },\n { value: 'listed', label: 'Listed' },\n { value: 'verified', label: 'Verified' },\n { value: 'hidden', label: 'Taken down' },\n]\n\n/** How the queue's clauses read in a refusal. */\nconst QUEUE_HEADERS: Readonly<Record<string, string>> = {\n reviewStatus: 'Status',\n takenDown: 'Taken down',\n}\nconst QUEUE_OPTIONS = {\n reviewStatus: STATUS_FILTERS.filter(\n (option) => option.value !== 'all' && option.value !== 'hidden',\n ),\n}\n\n/** Rows per page of each section. */\nconst QUEUE_PAGE = 50\n/** How long the search box waits for typing to stop before it asks again. */\nconst SEARCH_SETTLE_MS = 300\n\nconst NO_MORE: Record<ReviewQueueSection, boolean> = {\n queue: false,\n listed: false,\n verification: false,\n}\nconst FIRST_PAGES: Record<ReviewQueueSection, number> = {\n queue: 0,\n listed: 0,\n verification: 0,\n}\n\n/**\n * Staff marketplace review index (AGL-961).\n *\n * Scanning surface only: rows carry just enough to pick the right listing,\n * and every consequential action — verdicts, realm trust, takedown — lives\n * on the detail page. The previous version stacked all of it inline, which\n * meant the most destructive controls in the platform sat as same-weight\n * text buttons in a wall of caption text.\n *\n * The search box and the Status select are served by the route's QUERY\n * (AGL-3321): each section asks Firestore for its rows with the status and\n * the searched name word on it, so a plugin past the first hundred is found.\n * They used to be matched here over the hundred the route had read. Search\n * reads the listing name; a refusal or a one-word notice from the plan is\n * shown above the sections. Each section pages: the route reads every section\n * out to the furthest page asked plus one row, which is how a section knows\n * there is a next page (the window `usePagedCollection` reads, over a route).\n */\nexport function PluginReviewsQueue({ basePath }: { basePath: string }) {\n const { data: user } = useUser()\n const [queue, setQueue] = useState<QueueRow[]>([])\n const [listed, setListed] = useState<ListedRow[]>([])\n const [verificationRequests, setVerificationRequests] = useState<QueueRow[]>(\n [],\n )\n const [publishers, setPublishers] = useState<Record<string, string>>({})\n const [loaded, setLoaded] = useState(false)\n const [loadError, setLoadError] = useState<string | null>(null)\n const [search, setSearch] = useState('')\n const [asked, setAsked] = useState('')\n const [status, setStatus] = useState('all')\n const [pages, setPages] =\n useState<Record<ReviewQueueSection, number>>(FIRST_PAGES)\n /** Every section is read out to the furthest page any of them is on. */\n const limit = QUEUE_PAGE * (Math.max(...Object.values(pages)) + 1)\n const [more, setMore] = useState<Record<ReviewQueueSection, boolean>>(NO_MORE)\n const [refused, setRefused] = useState<\n Array<{ clause: ListFilterClause | 'search'; reason: string }>\n >([])\n const [notices, setNotices] = useState<string[]>([])\n\n // The search is asked once typing settles, not per keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setAsked(search.trim()), SEARCH_SETTLE_MS)\n return () => clearTimeout(timer)\n }, [search])\n // A new question starts from the first page.\n useEffect(() => {\n setPages(FIRST_PAGES)\n }, [asked, status])\n\n const refresh = useCallback(async () => {\n const params = new URLSearchParams()\n if (asked) params.set('q', asked)\n if (status !== 'all') params.set('status', status)\n params.set('limit', String(limit))\n const suffix = params.toString()\n const response = await authorizedFetch(\n user,\n `/api/marketplace/admin/reviews${suffix ? `?${suffix}` : ''}`,\n )\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n setLoadError(null)\n setQueue(payload?.queue ?? [])\n setListed(payload?.listed ?? [])\n setVerificationRequests(payload?.verificationRequests ?? [])\n setPublishers(payload?.publishers ?? {})\n setMore({ ...NO_MORE, ...(payload?.more ?? {}) })\n setRefused(payload?.refused ?? [])\n setNotices(payload?.notices ?? [])\n } else {\n // Named, not swallowed. An empty queue and a queue that could not be\n // read look identical, and only one of them means there is no work.\n setLoadError(\n payload?.error ?? `Loading the queue failed (${response.status})`,\n )\n }\n setLoaded(true)\n }, [user, asked, status, limit])\n\n useEffect(() => {\n if (user) void refresh()\n }, [user, refresh])\n\n const publisherName = useCallback(\n (profileId: string) => publishers[profileId] ?? profileId,\n [publishers],\n )\n\n // Every section is already what the query answered: nothing is matched\n // here, only the page on screen sliced out of the window read.\n const pageOf = <T,>(section: ReviewQueueSection, rows: T[]): T[] =>\n rows.slice(pages[section] * QUEUE_PAGE, (pages[section] + 1) * QUEUE_PAGE)\n const visibleQueue = pageOf('queue', queue)\n const visibleListed = pageOf('listed', listed)\n const visibleVerification = pageOf('verification', verificationRequests)\n const total: Record<ReviewQueueSection, number> = {\n queue: queue.length,\n listed: listed.length,\n verification: verificationRequests.length,\n }\n const filtering = asked.length > 0 || status !== 'all'\n const refusals = useMemo(\n () =>\n listQueryRefusals(refused, {\n fields: REVIEW_QUEUE_QUERY.fields,\n headers: QUEUE_HEADERS,\n options: QUEUE_OPTIONS,\n }),\n [refused],\n )\n const pager = (section: ReviewQueueSection, shown: number) => {\n const hasMore =\n total[section] > (pages[section] + 1) * QUEUE_PAGE || more[section]\n return pages[section] > 0 || hasMore ? (\n <ListPagination\n page={pages[section]}\n pageSize={QUEUE_PAGE}\n rowCount={shown}\n hasMore={hasMore}\n onPageChange={(page) =>\n setPages((previous) => ({ ...previous, [section]: page }))\n }\n />\n ) : null\n }\n\n const row = (\n key: string,\n listingId: string,\n name: string,\n profileId: string,\n chips: React.ReactNode,\n caption: string,\n ) => (\n <Stack\n key={key}\n spacing={0.25}\n sx={{\n py: 1,\n borderBottom: 1,\n borderColor: 'divider',\n '&:last-of-type': { borderBottom: 0 },\n }}\n >\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <AppLink href={`${basePath}/${encodeURIComponent(listingId)}`}>\n <Typography variant=\"subtitle2\" component=\"span\">\n {name}\n </Typography>\n </AppLink>\n {chips}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${publisherName(profileId)} · ${caption}`}\n </Typography>\n </Stack>\n )\n\n return (\n <>\n {/*\n The staff console's own topic, anchored. A `staffPages` entry carries\n one `docsTopic` and no anchor, and the staff console page is long\n enough that landing at the top of it is the AGL-2200 shape.\n */}\n <PageHeaderHelp topic=\"staffConsole\" anchor=\"#plugin-reviews\" />\n {/*\n The staff gate is the shell's: the generic staff route renders every\n plugin page inside `StaffOnly`, which renders nothing while the claim\n resolves and 404s a non-holder. Without it a non-staff visitor was\n told \"No plugin submissions waiting for review\" — the queue fetch\n 403s and the empty result reads as good news rather than a refusal\n (AGL-760).\n */}\n <Stack spacing={3}>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n placeholder=\"Search plugin names\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n sx={{ minWidth: 320 }}\n />\n <TextField\n size=\"small\"\n select\n value={status}\n onChange={(event) => setStatus(event.target.value)}\n sx={{ minWidth: 180 }}\n >\n {STATUS_FILTERS.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n {loadError ? (\n <Alert severity=\"error\">{loadError}</Alert>\n ) : null}\n <ListQueryNotices refused={refusals} notices={notices} />\n\n {!loaded ? (\n <Stack spacing={2}>\n <Skeleton variant=\"rounded\" height={140} />\n <Skeleton variant=\"rounded\" height={200} />\n </Stack>\n ) : (\n <>\n {/* Its own card, above the review queue (AGL-1217). A\n verification request is a different question from\n \"have these bytes been read\" — it is about the\n publisher, and it is answered by a different action.\n Folded into Awaiting review it would be invisible: a\n listing whose bytes are already approved does not\n appear there at all. Hidden when empty, so it costs\n nothing on the common day. */}\n {visibleVerification.length ? (\n <CardDisplay\n header={`Verification requested (${total.verification}${more.verification ? '+' : ''})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#verification-requests',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'These plugins are already live. The publisher has ' +\n 'asked us to vouch for who they are — granting adds ' +\n 'the badge and changes nothing about installability.'}\n </Typography>\n <Stack>\n {visibleVerification.map((entry) =>\n row(\n `verify-${entry.listingId}`,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color=\"info\"\n label=\"Verification requested\"\n />\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n </>,\n 'Awaiting a verification decision',\n ),\n )}\n {pager('verification', visibleVerification.length)}\n </Stack>\n </CardDisplay>\n ) : null}\n <CardDisplay\n header={`Awaiting review (${total.queue}${more.queue ? '+' : ''})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#review-queue',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Submitted and In review are not installable by anyone.'}\n </Typography>\n {visibleQueue.length ? (\n <Stack>\n {visibleQueue.map((entry) =>\n row(\n entry.listingId,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n {/* Same bar, smaller audience (AGL-968/995).\n Worth flagging so a reviewer knows the\n blast radius is one workspace — never so\n they review it more loosely. */}\n {entry.private ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Private\"\n />\n ) : null}\n {/* A private plugin has no marketplace listing\n page, so \"no license\" is not a finding. */}\n {entry.license || entry.private ? null : (\n <Chip\n size=\"small\"\n color=\"warning\"\n label=\"No license\"\n />\n )}\n </>,\n entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free',\n ),\n )}\n {pager('queue', visibleQueue.length)}\n </Stack>\n ) : (\n <Alert severity={filtering ? 'info' : 'success'}>\n {filtering\n ? 'No submissions match this filter.'\n : 'No plugin submissions waiting for review.'}\n </Alert>\n )}\n </CardDisplay>\n\n <CardDisplay\n header={`Listed plugins (${total.listed}${more.listed ? '+' : ''})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#listed-plugins',\n })}\n contentGutterX\n contentGutterY\n >\n {/* Listed vs Verified is the distinction reviewers get\n wrong (AGL-966): both are LIVE. Verified only adds a\n badge — it does not change who can install. */}\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Both states are live — installable by every workspace. ' +\n 'Verified additionally carries the reviewed badge on ' +\n 'its listing page; it does not change installability. ' +\n // The sentence above is false for a private row\n // (AGL-968/995), and it sits directly over the rows\n // it would misdescribe.\n 'Rows marked Private are the exception: approved and ' +\n 'live, but only for the org that published them.'}\n </Typography>\n {visibleListed.length ? (\n <Stack>\n {visibleListed.map((entry) =>\n row(\n entry.listingId,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.latestVersion || '—'}`}\n />\n {entry.private ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Private\"\n />\n ) : null}\n {entry.realmVersions ? (\n <Chip\n size=\"small\"\n color=\"success\"\n label={`${entry.realmVersions} realm-trusted`}\n />\n ) : null}\n {entry.hidden ? (\n <Chip\n size=\"small\"\n color=\"error\"\n label=\"Taken down\"\n />\n ) : null}\n </>,\n `${entry.versionCount} version${\n entry.versionCount === 1 ? '' : 's'\n }${entry.hidden && entry.hiddenReason ? ` · ${entry.hiddenReason}` : ''}`,\n ),\n )}\n {pager('listed', visibleListed.length)}\n </Stack>\n ) : (\n <Alert severity=\"info\">\n {filtering\n ? 'No listed plugins match this filter.'\n : 'No listed plugins yet.'}\n </Alert>\n )}\n </CardDisplay>\n </>\n )}\n </Stack>\n </>\n )\n}\n\nexport default PluginReviewsQueue\n"],"names":["PageHeaderHelp","AppLink","CardDisplay","ListPagination","ListQueryNotices","listQueryRefusals","Alert","Chip","MenuItem","Skeleton","Stack","TextField","Typography","useCallback","useEffect","useMemo","useState","useUser","authorizedFetch","pluginDocsHelp","reviewStatusMeaning","REVIEW_QUEUE_QUERY","STATUS_FILTERS","value","label","QUEUE_HEADERS","reviewStatus","takenDown","QUEUE_OPTIONS","filter","option","QUEUE_PAGE","SEARCH_SETTLE_MS","NO_MORE","queue","listed","verification","FIRST_PAGES","PluginReviewsQueue","basePath","data","user","setQueue","setListed","verificationRequests","setVerificationRequests","publishers","setPublishers","loaded","setLoaded","loadError","setLoadError","search","setSearch","asked","setAsked","status","setStatus","pages","setPages","limit","Math","max","Object","values","more","setMore","refused","setRefused","notices","setNotices","timer","setTimeout","trim","clearTimeout","refresh","params","URLSearchParams","set","String","suffix","toString","response","payload","json","catch","ok","error","publisherName","profileId","pageOf","section","rows","slice","visibleQueue","visibleListed","visibleVerification","total","length","filtering","refusals","fields","headers","options","pager","shown","hasMore","page","pageSize","rowCount","onPageChange","previous","row","key","listingId","name","chips","caption","spacing","sx","py","borderBottom","borderColor","useFlexGap","direction","alignItems","flexWrap","href","encodeURIComponent","variant","component","color","topic","anchor","size","placeholder","onChange","event","target","minWidth","select","map","severity","height","header","help","contentGutterX","contentGutterY","display","mb","entry","displayName","version","private","license","priceUsd","latestVersion","realmVersions","hidden","versionCount","hiddenReason"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-reviews-queue.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 { PageHeaderHelp } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } 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 type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n Alert,\n Chip,\n MenuItem,\n Skeleton,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n reviewStatusMeaning,\n starterReviewStatusMeaning,\n} from '../model/plugin-review-status'\nimport {\n REVIEW_QUEUE_QUERY,\n type ReviewQueueSection,\n} from '../model/listing-query'\n\ninterface QueueRow {\n listingId: string\n displayName: string\n description: string\n license: string\n categories: string[]\n profileId: string\n reviewStatus: string\n priceUsd: number\n version: string\n hidden: boolean\n /** Private plugin (AGL-968): reviewed identically, never listed. */\n private: boolean\n /** The publisher's standing ask for the Verified badge (AGL-1217). */\n verificationRequest?: { state?: string; requestedAt?: unknown } | null\n}\n\ninterface ListedRow {\n listingId: string\n displayName: string\n reviewStatus: string\n profileId: string\n latestVersion: string\n hidden: boolean\n hiddenReason: string\n realmVersions: number\n versionCount: number\n private: boolean\n}\n\n/** A site starter's row (AGL-4049): what a reviewer scans a starter by. */\ninterface StarterRow {\n listingId: string\n displayName: string\n profileId: string\n reviewStatus: string\n priceUsd: number\n pages: number\n version: string\n /** The version the last verdict was given on, or null before one. */\n reviewedVersion: string | null\n submittedAt: string | null\n hidden: boolean\n official: boolean\n}\n\nconst STATUS_FILTERS = [\n { value: 'all', label: 'All statuses' },\n { value: 'submitted', label: 'Submitted' },\n { value: 'in_review', label: 'In review' },\n { value: 'listed', label: 'Listed' },\n { value: 'verified', label: 'Verified' },\n // A rejected site starter stays rejected until its publisher publishes it\n // again (AGL-4049), so the Starters section needs a way to find it.\n { value: 'rejected', label: 'Rejected' },\n { value: 'hidden', label: 'Taken down' },\n]\n\n/** How the queue's clauses read in a refusal. */\nconst QUEUE_HEADERS: Readonly<Record<string, string>> = {\n reviewStatus: 'Status',\n takenDown: 'Taken down',\n}\nconst QUEUE_OPTIONS = {\n reviewStatus: STATUS_FILTERS.filter(\n (option) => option.value !== 'all' && option.value !== 'hidden',\n ),\n}\n\n/** Rows per page of each section. */\nconst QUEUE_PAGE = 50\n/** How long the search box waits for typing to stop before it asks again. */\nconst SEARCH_SETTLE_MS = 300\n\nconst NO_MORE: Record<ReviewQueueSection, boolean> = {\n queue: false,\n listed: false,\n verification: false,\n starters: false,\n}\nconst FIRST_PAGES: Record<ReviewQueueSection, number> = {\n queue: 0,\n listed: 0,\n verification: 0,\n starters: 0,\n}\n\nconst plural = (count: number, one: string) =>\n `${count} ${one}${count === 1 ? '' : 's'}`\n\n/**\n * Staff marketplace review index (AGL-961).\n *\n * Scanning surface only: rows carry just enough to pick the right listing,\n * and every consequential action — verdicts, realm trust, takedown — lives\n * on the detail page. The previous version stacked all of it inline, which\n * meant the most destructive controls in the platform sat as same-weight\n * text buttons in a wall of caption text.\n *\n * The search box and the Status select are served by the route's QUERY\n * (AGL-3321): each section asks Firestore for its rows with the status and\n * the searched name word on it, so a plugin past the first hundred is found.\n * They used to be matched here over the hundred the route had read. Search\n * reads the listing name; a refusal or a one-word notice from the plan is\n * shown above the sections. Each section pages: the route reads every section\n * out to the furthest page asked plus one row, which is how a section knows\n * there is a next page (the window `usePagedCollection` reads, over a route).\n */\nexport function PluginReviewsQueue({ basePath }: { basePath: string }) {\n const { data: user } = useUser()\n const [queue, setQueue] = useState<QueueRow[]>([])\n const [listed, setListed] = useState<ListedRow[]>([])\n const [verificationRequests, setVerificationRequests] = useState<QueueRow[]>(\n [],\n )\n const [starters, setStarters] = useState<StarterRow[]>([])\n const [publishers, setPublishers] = useState<Record<string, string>>({})\n const [loaded, setLoaded] = useState(false)\n const [loadError, setLoadError] = useState<string | null>(null)\n const [search, setSearch] = useState('')\n const [asked, setAsked] = useState('')\n const [status, setStatus] = useState('all')\n const [pages, setPages] =\n useState<Record<ReviewQueueSection, number>>(FIRST_PAGES)\n /** Every section is read out to the furthest page any of them is on. */\n const limit = QUEUE_PAGE * (Math.max(...Object.values(pages)) + 1)\n const [more, setMore] = useState<Record<ReviewQueueSection, boolean>>(NO_MORE)\n const [refused, setRefused] = useState<\n Array<{ clause: ListFilterClause | 'search'; reason: string }>\n >([])\n const [notices, setNotices] = useState<string[]>([])\n\n // The search is asked once typing settles, not per keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setAsked(search.trim()), SEARCH_SETTLE_MS)\n return () => clearTimeout(timer)\n }, [search])\n // A new question starts from the first page.\n useEffect(() => {\n setPages(FIRST_PAGES)\n }, [asked, status])\n\n const refresh = useCallback(async () => {\n const params = new URLSearchParams()\n if (asked) params.set('q', asked)\n if (status !== 'all') params.set('status', status)\n params.set('limit', String(limit))\n const suffix = params.toString()\n const response = await authorizedFetch(\n user,\n `/api/marketplace/admin/reviews${suffix ? `?${suffix}` : ''}`,\n )\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n setLoadError(null)\n setQueue(payload?.queue ?? [])\n setListed(payload?.listed ?? [])\n setVerificationRequests(payload?.verificationRequests ?? [])\n setStarters(payload?.starters ?? [])\n setPublishers(payload?.publishers ?? {})\n setMore({ ...NO_MORE, ...(payload?.more ?? {}) })\n setRefused(payload?.refused ?? [])\n setNotices(payload?.notices ?? [])\n } else {\n // Named, not swallowed. An empty queue and a queue that could not be\n // read look identical, and only one of them means there is no work.\n setLoadError(\n payload?.error ?? `Loading the queue failed (${response.status})`,\n )\n }\n setLoaded(true)\n }, [user, asked, status, limit])\n\n useEffect(() => {\n if (user) void refresh()\n }, [user, refresh])\n\n const publisherName = useCallback(\n (profileId: string) => publishers[profileId] ?? profileId,\n [publishers],\n )\n\n // Every section is already what the query answered: nothing is matched\n // here, only the page on screen sliced out of the window read.\n const pageOf = <T,>(section: ReviewQueueSection, rows: T[]): T[] =>\n rows.slice(pages[section] * QUEUE_PAGE, (pages[section] + 1) * QUEUE_PAGE)\n const visibleQueue = pageOf('queue', queue)\n const visibleListed = pageOf('listed', listed)\n const visibleVerification = pageOf('verification', verificationRequests)\n const visibleStarters = pageOf('starters', starters)\n const total: Record<ReviewQueueSection, number> = {\n queue: queue.length,\n listed: listed.length,\n verification: verificationRequests.length,\n starters: starters.length,\n }\n const filtering = asked.length > 0 || status !== 'all'\n const refusals = useMemo(\n () =>\n listQueryRefusals(refused, {\n fields: REVIEW_QUEUE_QUERY.fields,\n headers: QUEUE_HEADERS,\n options: QUEUE_OPTIONS,\n }),\n [refused],\n )\n const pager = (section: ReviewQueueSection, shown: number) => {\n const hasMore =\n total[section] > (pages[section] + 1) * QUEUE_PAGE || more[section]\n return pages[section] > 0 || hasMore ? (\n <ListPagination\n page={pages[section]}\n pageSize={QUEUE_PAGE}\n rowCount={shown}\n hasMore={hasMore}\n onPageChange={(page) =>\n setPages((previous) => ({ ...previous, [section]: page }))\n }\n />\n ) : null\n }\n\n const row = (\n key: string,\n listingId: string,\n name: string,\n profileId: string,\n chips: React.ReactNode,\n caption: string,\n ) => (\n <Stack\n key={key}\n spacing={0.25}\n sx={{\n py: 1,\n borderBottom: 1,\n borderColor: 'divider',\n '&:last-of-type': { borderBottom: 0 },\n }}\n >\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <AppLink href={`${basePath}/${encodeURIComponent(listingId)}`}>\n <Typography variant=\"subtitle2\" component=\"span\">\n {name}\n </Typography>\n </AppLink>\n {chips}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${publisherName(profileId)} · ${caption}`}\n </Typography>\n </Stack>\n )\n\n return (\n <>\n {/*\n The staff console's own topic, anchored. A `staffPages` entry carries\n one `docsTopic` and no anchor, and the staff console page is long\n enough that landing at the top of it is the AGL-2200 shape.\n */}\n <PageHeaderHelp topic=\"staffConsole\" anchor=\"#plugin-reviews\" />\n {/*\n The staff gate is the shell's: the generic staff route renders every\n plugin page inside `StaffOnly`, which renders nothing while the claim\n resolves and 404s a non-holder. Without it a non-staff visitor was\n told \"No plugin submissions waiting for review\" — the queue fetch\n 403s and the empty result reads as good news rather than a refusal\n (AGL-760).\n */}\n <Stack spacing={3}>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n placeholder=\"Search plugin and starter names\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n sx={{ minWidth: 320 }}\n />\n <TextField\n size=\"small\"\n select\n value={status}\n onChange={(event) => setStatus(event.target.value)}\n sx={{ minWidth: 180 }}\n >\n {STATUS_FILTERS.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n {loadError ? (\n <Alert severity=\"error\">{loadError}</Alert>\n ) : null}\n <ListQueryNotices refused={refusals} notices={notices} />\n\n {!loaded ? (\n <Stack spacing={2}>\n <Skeleton variant=\"rounded\" height={140} />\n <Skeleton variant=\"rounded\" height={200} />\n </Stack>\n ) : (\n <>\n {/* Its own card, above the review queue (AGL-1217). A\n verification request is a different question from\n \"have these bytes been read\" — it is about the\n publisher, and it is answered by a different action.\n Folded into Awaiting review it would be invisible: a\n listing whose bytes are already approved does not\n appear there at all. Hidden when empty, so it costs\n nothing on the common day. */}\n {visibleVerification.length ? (\n <CardDisplay\n header={`Verification requested (${total.verification}${more.verification ? '+' : ''})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#verification-requests',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'These plugins are already live. The publisher has ' +\n 'asked us to vouch for who they are — granting adds ' +\n 'the badge and changes nothing about installability.'}\n </Typography>\n <Stack>\n {visibleVerification.map((entry) =>\n row(\n `verify-${entry.listingId}`,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color=\"info\"\n label=\"Verification requested\"\n />\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n </>,\n 'Awaiting a verification decision',\n ),\n )}\n {pager('verification', visibleVerification.length)}\n </Stack>\n </CardDisplay>\n ) : null}\n <CardDisplay\n header={`Awaiting review (${total.queue}${more.queue ? '+' : ''})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#review-queue',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Submitted and In review are not installable by anyone.'}\n </Typography>\n {visibleQueue.length ? (\n <Stack>\n {visibleQueue.map((entry) =>\n row(\n entry.listingId,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n {/* Same bar, smaller audience (AGL-968/995).\n Worth flagging so a reviewer knows the\n blast radius is one workspace — never so\n they review it more loosely. */}\n {entry.private ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Private\"\n />\n ) : null}\n {/* A private plugin has no marketplace listing\n page, so \"no license\" is not a finding. */}\n {entry.license || entry.private ? null : (\n <Chip\n size=\"small\"\n color=\"warning\"\n label=\"No license\"\n />\n )}\n </>,\n entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free',\n ),\n )}\n {pager('queue', visibleQueue.length)}\n </Stack>\n ) : (\n <Alert severity={filtering ? 'info' : 'success'}>\n {filtering\n ? 'No submissions match this filter.'\n : 'No plugin submissions waiting for review.'}\n </Alert>\n )}\n </CardDisplay>\n\n {/* Site starters (AGL-4049), their own card: a starter is a\n whole site someone creates the moment they pick it, not\n code, so a row says what it is made of and costs rather\n than a version's review state. With All statuses it holds\n the ones waiting; a status names the rest. */}\n <CardDisplay\n header={`${status === 'all' ? 'Site starters awaiting review' : 'Site starters'} (${total.starters}${more.starters ? '+' : ''})`}\n help={pluginDocsHelp('staffConsole', {\n anchor: '#plugin-reviews-site-starters',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Nobody but its publisher can create a site from a ' +\n 'starter until it is listed. Pick a status above to see ' +\n 'listed, rejected or taken-down starters.'}\n </Typography>\n {visibleStarters.length ? (\n <Stack>\n {visibleStarters.map((entry) => {\n const meaning = starterReviewStatusMeaning(entry.reviewStatus)\n return row(\n `starter-${entry.listingId}`,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip size=\"small\" color={meaning.color} label={meaning.label} />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n {/* Listed stays listed when it is published again,\n so a newer version than the one reviewed is\n what a reviewer looks for under Listed. */}\n {entry.reviewedVersion &&\n entry.reviewedVersion !== entry.version ? (\n <Chip\n size=\"small\"\n color=\"warning\"\n label={`v${entry.version} since review (v${entry.reviewedVersion})`}\n />\n ) : null}\n {entry.official ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Official\" />\n ) : null}\n {entry.hidden ? (\n <Chip size=\"small\" color=\"error\" label=\"Taken down\" />\n ) : null}\n </>,\n [\n plural(entry.pages, 'page'),\n entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free',\n entry.submittedAt\n ? `Submitted ${new Date(entry.submittedAt).toLocaleDateString()}`\n : '',\n ]\n .filter(Boolean)\n .join(' · '),\n )\n })}\n {pager('starters', visibleStarters.length)}\n </Stack>\n ) : (\n <Alert severity={filtering ? 'info' : 'success'}>\n {filtering\n ? 'No site starters match this filter.'\n : 'No site starters waiting for review.'}\n </Alert>\n )}\n </CardDisplay>\n\n <CardDisplay\n header={`Listed plugins (${total.listed}${more.listed ? '+' : ''})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#listed-plugins',\n })}\n contentGutterX\n contentGutterY\n >\n {/* Listed vs Verified is the distinction reviewers get\n wrong (AGL-966): both are LIVE. Verified only adds a\n badge — it does not change who can install. */}\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Both states are live — installable by every workspace. ' +\n 'Verified additionally carries the reviewed badge on ' +\n 'its listing page; it does not change installability. ' +\n // The sentence above is false for a private row\n // (AGL-968/995), and it sits directly over the rows\n // it would misdescribe.\n 'Rows marked Private are the exception: approved and ' +\n 'live, but only for the org that published them.'}\n </Typography>\n {visibleListed.length ? (\n <Stack>\n {visibleListed.map((entry) =>\n row(\n entry.listingId,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.latestVersion || '—'}`}\n />\n {entry.private ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Private\"\n />\n ) : null}\n {entry.realmVersions ? (\n <Chip\n size=\"small\"\n color=\"success\"\n label={`${entry.realmVersions} realm-trusted`}\n />\n ) : null}\n {entry.hidden ? (\n <Chip\n size=\"small\"\n color=\"error\"\n label=\"Taken down\"\n />\n ) : null}\n </>,\n `${entry.versionCount} version${\n entry.versionCount === 1 ? '' : 's'\n }${entry.hidden && entry.hiddenReason ? ` · ${entry.hiddenReason}` : ''}`,\n ),\n )}\n {pager('listed', visibleListed.length)}\n </Stack>\n ) : (\n <Alert severity=\"info\">\n {filtering\n ? 'No listed plugins match this filter.'\n : 'No listed plugins yet.'}\n </Alert>\n )}\n </CardDisplay>\n </>\n )}\n </Stack>\n </>\n )\n}\n\nexport default PluginReviewsQueue\n"],"names":["PageHeaderHelp","AppLink","CardDisplay","ListPagination","ListQueryNotices","listQueryRefusals","Alert","Chip","MenuItem","Skeleton","Stack","TextField","Typography","useCallback","useEffect","useMemo","useState","useUser","authorizedFetch","pluginDocsHelp","reviewStatusMeaning","starterReviewStatusMeaning","REVIEW_QUEUE_QUERY","STATUS_FILTERS","value","label","QUEUE_HEADERS","reviewStatus","takenDown","QUEUE_OPTIONS","filter","option","QUEUE_PAGE","SEARCH_SETTLE_MS","NO_MORE","queue","listed","verification","starters","FIRST_PAGES","plural","count","one","PluginReviewsQueue","basePath","data","user","setQueue","setListed","verificationRequests","setVerificationRequests","setStarters","publishers","setPublishers","loaded","setLoaded","loadError","setLoadError","search","setSearch","asked","setAsked","status","setStatus","pages","setPages","limit","Math","max","Object","values","more","setMore","refused","setRefused","notices","setNotices","timer","setTimeout","trim","clearTimeout","refresh","params","URLSearchParams","set","String","suffix","toString","response","payload","json","catch","ok","error","publisherName","profileId","pageOf","section","rows","slice","visibleQueue","visibleListed","visibleVerification","visibleStarters","total","length","filtering","refusals","fields","headers","options","pager","shown","hasMore","page","pageSize","rowCount","onPageChange","previous","row","key","listingId","name","chips","caption","spacing","sx","py","borderBottom","borderColor","useFlexGap","direction","alignItems","flexWrap","href","encodeURIComponent","variant","component","color","topic","anchor","size","placeholder","onChange","event","target","minWidth","select","map","severity","height","header","help","contentGutterX","contentGutterY","display","mb","entry","displayName","version","private","license","priceUsd","meaning","reviewedVersion","official","hidden","submittedAt","Date","toLocaleDateString","Boolean","join","latestVersion","realmVersions","versionCount","hiddenReason"],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type ConsoleStaffPagePaths } from '@aglyn/aglyn';
18
+ /** One site starter, as the review route answers for its page (AGL-4049). */
19
+ export interface SiteStarterReviewDetail {
20
+ artifactType: 'siteStarter';
21
+ listingId: string;
22
+ displayName: string;
23
+ description: string;
24
+ category: string | null;
25
+ previewImageUrl: string | null;
26
+ priceUsd: number;
27
+ official: boolean;
28
+ reviewStatus: string;
29
+ rejectionReason: string;
30
+ rejectionCategory: string;
31
+ /** The version the last List or Reject was given on. */
32
+ reviewedVersion: string | null;
33
+ hidden: boolean;
34
+ hiddenReason: string;
35
+ unpublished: boolean;
36
+ latestVersion: string;
37
+ /** The version the pages, theme and checklist below are read from. */
38
+ reviewVersion: string;
39
+ mode: 'base' | 'ai';
40
+ facets: {
41
+ kind: string | null;
42
+ base: string | null;
43
+ fonts: string | null;
44
+ pages: number;
45
+ mode: string;
46
+ };
47
+ pages: Array<{
48
+ name: string;
49
+ slug: string | null;
50
+ published: boolean;
51
+ }>;
52
+ layout: {
53
+ name: string;
54
+ } | null;
55
+ theme: {
56
+ kind: 'builtin';
57
+ base: string;
58
+ fonts: string | null;
59
+ brand: {
60
+ light: Record<string, string>;
61
+ dark?: Record<string, string>;
62
+ };
63
+ } | {
64
+ kind: 'full';
65
+ } | null;
66
+ publisherId: string;
67
+ publisherName: string;
68
+ publisherSlug: string | null;
69
+ publisherAgreement: {
70
+ version: string | null;
71
+ required: string;
72
+ state: 'none' | 'outdated' | 'current';
73
+ };
74
+ sourceSite: {
75
+ hostId: string;
76
+ name: string | null;
77
+ subdomain: string | null;
78
+ cname: string | null;
79
+ } | null;
80
+ versions: Array<{
81
+ version: string;
82
+ publishedAt: string | null;
83
+ pages: number;
84
+ }>;
85
+ checklist: Record<string, {
86
+ by: string | null;
87
+ }>;
88
+ checklistOutstanding: string[];
89
+ }
90
+ export interface SiteStarterReviewBodyProps {
91
+ detail: SiteStarterReviewDetail;
92
+ busy: boolean;
93
+ /** Sends one action to the review route, then reads the page again. */
94
+ post: (payload: Record<string, unknown>, success: string) => Promise<void>;
95
+ /** Reads the page for another version. */
96
+ onSelectVersion: (version: string) => void;
97
+ staffPaths?: ConsoleStaffPagePaths;
98
+ }
99
+ /**
100
+ * A site starter's review page (AGL-4049): what a site created from it gets,
101
+ * the starter checklist, and its two verdicts.
102
+ *
103
+ * A starter is not code. Nothing on the plugin page — manifest, verifier,
104
+ * per-version approval, realm trust — has anything to act on in it, so this
105
+ * page reads what a person's new site is made of instead: every page, with a
106
+ * link to it on the site it was published from, the layout they sit in, the
107
+ * theme and its brand colors in both schemes, the picture and facets a search
108
+ * shows. List waits for every checklist item on the newest version; Reject
109
+ * takes one of a starter's own reasons, which its publisher is told.
110
+ */
111
+ export declare function SiteStarterReviewBody({ detail, busy, post, onSelectVersion, staffPaths, }: SiteStarterReviewBodyProps): import("react").JSX.Element;
112
+ export declare namespace SiteStarterReviewBody {
113
+ var displayName: string;
114
+ }
115
+ export default SiteStarterReviewBody;