@aglyn/plugins-marketplace 1.0.0-beta.199 → 1.0.0-beta.201

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 (59) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/marketplace-browse.component.js +104 -169
  3. package/src/lib/components/marketplace-browse.component.js.map +1 -1
  4. package/src/lib/components/marketplace-reports-page.component.js +9 -0
  5. package/src/lib/components/marketplace-reports-page.component.js.map +1 -1
  6. package/src/lib/components/org-licences-panel.component.js +212 -170
  7. package/src/lib/components/org-licences-panel.component.js.map +1 -1
  8. package/src/lib/components/org-seller-panel.component.js +23 -5
  9. package/src/lib/components/org-seller-panel.component.js.map +1 -1
  10. package/src/lib/components/plugin-reviews-queue.component.d.ts +9 -0
  11. package/src/lib/components/plugin-reviews-queue.component.js +190 -114
  12. package/src/lib/components/plugin-reviews-queue.component.js.map +1 -1
  13. package/src/lib/hooks/use-listing-name-lookup.d.ts +34 -0
  14. package/src/lib/hooks/use-listing-name-lookup.js +131 -0
  15. package/src/lib/hooks/use-listing-name-lookup.js.map +1 -0
  16. package/src/lib/model/listing-query.d.ts +140 -0
  17. package/src/lib/model/listing-query.js +393 -0
  18. package/src/lib/model/listing-query.js.map +1 -0
  19. package/src/lib/model/marketplace.d.ts +11 -0
  20. package/src/lib/model/marketplace.js.map +1 -1
  21. package/src/lib/server/admin-reports.js +30 -12
  22. package/src/lib/server/admin-reports.js.map +1 -1
  23. package/src/lib/server/admin-reviews.js +180 -128
  24. package/src/lib/server/admin-reviews.js.map +1 -1
  25. package/src/lib/server/admin-trust.js +2 -1
  26. package/src/lib/server/admin-trust.js.map +1 -1
  27. package/src/lib/server/billing-webhook.js +13 -1
  28. package/src/lib/server/billing-webhook.js.map +1 -1
  29. package/src/lib/server/checkout.js +3 -1
  30. package/src/lib/server/checkout.js.map +1 -1
  31. package/src/lib/server/install-email-starter.js +9 -2
  32. package/src/lib/server/install-email-starter.js.map +1 -1
  33. package/src/lib/server/install-layout.js +17 -5
  34. package/src/lib/server/install-layout.js.map +1 -1
  35. package/src/lib/server/install-template.js +15 -5
  36. package/src/lib/server/install-template.js.map +1 -1
  37. package/src/lib/server/install.js +10 -2
  38. package/src/lib/server/install.js.map +1 -1
  39. package/src/lib/server/listing-query-fields.d.ts +38 -0
  40. package/src/lib/server/listing-query-fields.js +50 -0
  41. package/src/lib/server/listing-query-fields.js.map +1 -0
  42. package/src/lib/server/publish-dataset-schema.js +6 -2
  43. package/src/lib/server/publish-dataset-schema.js.map +1 -1
  44. package/src/lib/server/publish-email-starter.js +6 -2
  45. package/src/lib/server/publish-email-starter.js.map +1 -1
  46. package/src/lib/server/publish-email-template.js +6 -2
  47. package/src/lib/server/publish-email-template.js.map +1 -1
  48. package/src/lib/server/publish-layout.js +6 -2
  49. package/src/lib/server/publish-layout.js.map +1 -1
  50. package/src/lib/server/publish-plugin.js +75 -7
  51. package/src/lib/server/publish-plugin.js.map +1 -1
  52. package/src/lib/server/publish-template.js +6 -2
  53. package/src/lib/server/publish-template.js.map +1 -1
  54. package/src/lib/server/publish-theme.js +6 -2
  55. package/src/lib/server/publish-theme.js.map +1 -1
  56. package/src/lib/server/publish.js +6 -2
  57. package/src/lib/server/publish.js.map +1 -1
  58. package/src/lib/server/update-artifact.js +18 -4
  59. package/src/lib/server/update-artifact.js.map +1 -1
@@ -14,15 +14,19 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
17
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
19
  import { PageHeaderHelp } from "@aglyn/aglyn";
19
20
  import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
21
+ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
22
+ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
20
23
  import { Alert, Chip, MenuItem, Skeleton, Stack, TextField, Typography } from "@mui/material";
21
24
  import { useCallback, useEffect, useMemo, useState } from "react";
22
25
  import { useUser } from "@aglyn/tenant-feature-instance";
23
26
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
24
27
  import { pluginDocsHelp } from "@aglyn/aglyn";
25
28
  import { reviewStatusMeaning } from "../model/plugin-review-status.js";
29
+ import { REVIEW_QUEUE_QUERY } from "../model/listing-query.js";
26
30
  const STATUS_FILTERS = [
27
31
  {
28
32
  value: 'all',
@@ -49,6 +53,25 @@ const STATUS_FILTERS = [
49
53
  label: 'Taken down'
50
54
  }
51
55
  ];
56
+ /** How the queue's clauses read in a refusal. */ const QUEUE_HEADERS = {
57
+ reviewStatus: 'Status',
58
+ takenDown: 'Taken down'
59
+ };
60
+ const QUEUE_OPTIONS = {
61
+ reviewStatus: STATUS_FILTERS.filter((option)=>option.value !== 'all' && option.value !== 'hidden')
62
+ };
63
+ /** Rows per page of each section. */ const QUEUE_PAGE = 50;
64
+ /** How long the search box waits for typing to stop before it asks again. */ const SEARCH_SETTLE_MS = 300;
65
+ const NO_MORE = {
66
+ queue: false,
67
+ listed: false,
68
+ verification: false
69
+ };
70
+ const FIRST_PAGES = {
71
+ queue: 0,
72
+ listed: 0,
73
+ verification: 0
74
+ };
52
75
  /**
53
76
  * Staff marketplace review index (AGL-961).
54
77
  *
@@ -57,6 +80,15 @@ const STATUS_FILTERS = [
57
80
  * on the detail page. The previous version stacked all of it inline, which
58
81
  * meant the most destructive controls in the platform sat as same-weight
59
82
  * text buttons in a wall of caption text.
83
+ *
84
+ * The search box and the Status select are served by the route's QUERY
85
+ * (AGL-3321): each section asks Firestore for its rows with the status and
86
+ * the searched name word on it, so a plugin past the first hundred is found.
87
+ * They used to be matched here over the hundred the route had read. Search
88
+ * reads the listing name; a refusal or a one-word notice from the plan is
89
+ * shown above the sections. Each section pages: the route reads every section
90
+ * out to the furthest page asked plus one row, which is how a section knows
91
+ * there is a next page (the window `usePagedCollection` reads, over a route).
60
92
  */ export function PluginReviewsQueue({ basePath }) {
61
93
  const { data: user } = useUser();
62
94
  const [queue, setQueue] = useState([]);
@@ -66,26 +98,57 @@ const STATUS_FILTERS = [
66
98
  const [loaded, setLoaded] = useState(false);
67
99
  const [loadError, setLoadError] = useState(null);
68
100
  const [search, setSearch] = useState('');
101
+ const [asked, setAsked] = useState('');
69
102
  const [status, setStatus] = useState('all');
103
+ const [pages, setPages] = useState(FIRST_PAGES);
104
+ /** Every section is read out to the furthest page any of them is on. */ const limit = QUEUE_PAGE * (Math.max(...Object.values(pages)) + 1);
105
+ const [more, setMore] = useState(NO_MORE);
106
+ const [refused, setRefused] = useState([]);
107
+ const [notices, setNotices] = useState([]);
108
+ // The search is asked once typing settles, not per keystroke.
109
+ useEffect(()=>{
110
+ const timer = setTimeout(()=>setAsked(search.trim()), SEARCH_SETTLE_MS);
111
+ return ()=>clearTimeout(timer);
112
+ }, [
113
+ search
114
+ ]);
115
+ // A new question starts from the first page.
116
+ useEffect(()=>{
117
+ setPages(FIRST_PAGES);
118
+ }, [
119
+ asked,
120
+ status
121
+ ]);
70
122
  const refresh = useCallback(async ()=>{
71
- const response = await authorizedFetch(user, '/api/marketplace/admin/reviews');
123
+ const params = new URLSearchParams();
124
+ if (asked) params.set('q', asked);
125
+ if (status !== 'all') params.set('status', status);
126
+ params.set('limit', String(limit));
127
+ const suffix = params.toString();
128
+ const response = await authorizedFetch(user, `/api/marketplace/admin/reviews${suffix ? `?${suffix}` : ''}`);
72
129
  const payload = await response.json().catch(()=>({}));
73
130
  if (response.ok) {
74
- var _ref, _ref1, _ref2, _ref3;
131
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6;
75
132
  setLoadError(null);
76
133
  setQueue((_ref = payload == null ? void 0 : payload.queue) != null ? _ref : []);
77
134
  setListed((_ref1 = payload == null ? void 0 : payload.listed) != null ? _ref1 : []);
78
135
  setVerificationRequests((_ref2 = payload == null ? void 0 : payload.verificationRequests) != null ? _ref2 : []);
79
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 : []);
80
140
  } else {
81
- var _ref4;
141
+ var _ref7;
82
142
  // Named, not swallowed. An empty queue and a queue that could not be
83
143
  // read look identical, and only one of them means there is no work.
84
- setLoadError((_ref4 = payload == null ? void 0 : payload.error) != null ? _ref4 : `Loading the queue failed (${response.status})`);
144
+ setLoadError((_ref7 = payload == null ? void 0 : payload.error) != null ? _ref7 : `Loading the queue failed (${response.status})`);
85
145
  }
86
146
  setLoaded(true);
87
147
  }, [
88
- user
148
+ user,
149
+ asked,
150
+ status,
151
+ limit
89
152
  ]);
90
153
  useEffect(()=>{
91
154
  if (user) void refresh();
@@ -99,37 +162,37 @@ const STATUS_FILTERS = [
99
162
  }, [
100
163
  publishers
101
164
  ]);
102
- // One predicate for both sections, so a search never means two things.
103
- const matches = useCallback((row)=>{
104
- const term = search.trim().toLowerCase();
105
- if (term && ![
106
- row.displayName,
107
- row.listingId,
108
- publisherName(row.profileId)
109
- ].some((field)=>String(field).toLowerCase().includes(term))) {
110
- return false;
111
- }
112
- if (status === 'all') return true;
113
- if (status === 'hidden') return row.hidden;
114
- return row.reviewStatus === status;
115
- }, [
116
- search,
117
- status,
118
- publisherName
119
- ]);
120
- const visibleQueue = useMemo(()=>queue.filter(matches), [
121
- queue,
122
- matches
123
- ]);
124
- const visibleListed = useMemo(()=>listed.filter(matches), [
125
- listed,
126
- matches
165
+ // Every section is already what the query answered: nothing is matched
166
+ // here, only the page on screen sliced out of the window read.
167
+ const pageOf = (section, rows)=>rows.slice(pages[section] * QUEUE_PAGE, (pages[section] + 1) * QUEUE_PAGE);
168
+ const visibleQueue = pageOf('queue', queue);
169
+ const visibleListed = pageOf('listed', listed);
170
+ const visibleVerification = pageOf('verification', verificationRequests);
171
+ const total = {
172
+ queue: queue.length,
173
+ listed: listed.length,
174
+ verification: verificationRequests.length
175
+ };
176
+ const filtering = asked.length > 0 || status !== 'all';
177
+ const refusals = useMemo(()=>listQueryRefusals(refused, {
178
+ fields: REVIEW_QUEUE_QUERY.fields,
179
+ headers: QUEUE_HEADERS,
180
+ options: QUEUE_OPTIONS
181
+ }), [
182
+ refused
127
183
  ]);
128
- const visibleVerification = useMemo(()=>verificationRequests.filter(matches), [
129
- verificationRequests,
130
- matches
131
- ]);
132
- const filtering = search.trim().length > 0 || status !== 'all';
184
+ const pager = (section, shown)=>{
185
+ const hasMore = total[section] > (pages[section] + 1) * QUEUE_PAGE || more[section];
186
+ return pages[section] > 0 || hasMore ? /*#__PURE__*/ _jsx(ListPagination, {
187
+ page: pages[section],
188
+ pageSize: QUEUE_PAGE,
189
+ rowCount: shown,
190
+ hasMore: hasMore,
191
+ onPageChange: (page)=>setPages((previous)=>_extends({}, previous, {
192
+ [section]: page
193
+ }))
194
+ }) : null;
195
+ };
133
196
  const row = (key, listingId, name, profileId, chips, caption)=>/*#__PURE__*/ _jsxs(Stack, {
134
197
  spacing: 0.25,
135
198
  sx: {
@@ -186,7 +249,7 @@ const STATUS_FILTERS = [
186
249
  children: [
187
250
  /*#__PURE__*/ _jsx(TextField, {
188
251
  size: "small",
189
- placeholder: "Search name, publisher or listing id",
252
+ placeholder: "Search plugin names",
190
253
  value: search,
191
254
  onChange: (event)=>setSearch(event.target.value),
192
255
  sx: {
@@ -212,6 +275,10 @@ const STATUS_FILTERS = [
212
275
  severity: "error",
213
276
  children: loadError
214
277
  }) : null,
278
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
279
+ refused: refusals,
280
+ notices: notices
281
+ }),
215
282
  !loaded ? /*#__PURE__*/ _jsxs(Stack, {
216
283
  spacing: 2,
217
284
  children: [
@@ -227,7 +294,7 @@ const STATUS_FILTERS = [
227
294
  }) : /*#__PURE__*/ _jsxs(_Fragment, {
228
295
  children: [
229
296
  visibleVerification.length ? /*#__PURE__*/ _jsxs(CardDisplay, {
230
- header: `Verification requested (${visibleVerification.length})`,
297
+ header: `Verification requested (${total.verification}${more.verification ? '+' : ''})`,
231
298
  help: pluginDocsHelp('publisherHandbook', {
232
299
  anchor: '#asking-to-be-verified',
233
300
  excerpt: 'Publishers who asked to be VERIFIED — a claim about who they are, ' + 'separate from reviewing any one version.'
@@ -244,31 +311,34 @@ const STATUS_FILTERS = [
244
311
  },
245
312
  children: 'These plugins are already live. The publisher has ' + 'asked us to vouch for who they are — granting adds ' + 'the badge and changes nothing about installability.'
246
313
  }),
247
- /*#__PURE__*/ _jsx(Stack, {
248
- children: visibleVerification.map((entry)=>row(`verify-${entry.listingId}`, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
249
- children: [
250
- /*#__PURE__*/ _jsx(Chip, {
251
- size: "small",
252
- color: "info",
253
- label: "Verification requested"
254
- }),
255
- /*#__PURE__*/ _jsx(Chip, {
256
- size: "small",
257
- color: reviewStatusMeaning(entry.reviewStatus).color,
258
- label: reviewStatusMeaning(entry.reviewStatus).label
259
- }),
260
- /*#__PURE__*/ _jsx(Chip, {
261
- size: "small",
262
- variant: "outlined",
263
- label: `v${entry.version || '—'}`
264
- })
265
- ]
266
- }), 'Awaiting a verification decision'))
314
+ /*#__PURE__*/ _jsxs(Stack, {
315
+ children: [
316
+ visibleVerification.map((entry)=>row(`verify-${entry.listingId}`, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
317
+ children: [
318
+ /*#__PURE__*/ _jsx(Chip, {
319
+ size: "small",
320
+ color: "info",
321
+ label: "Verification requested"
322
+ }),
323
+ /*#__PURE__*/ _jsx(Chip, {
324
+ size: "small",
325
+ color: reviewStatusMeaning(entry.reviewStatus).color,
326
+ label: reviewStatusMeaning(entry.reviewStatus).label
327
+ }),
328
+ /*#__PURE__*/ _jsx(Chip, {
329
+ size: "small",
330
+ variant: "outlined",
331
+ label: `v${entry.version || '—'}`
332
+ })
333
+ ]
334
+ }), 'Awaiting a verification decision')),
335
+ pager('verification', visibleVerification.length)
336
+ ]
267
337
  })
268
338
  ]
269
339
  }) : null,
270
340
  /*#__PURE__*/ _jsxs(CardDisplay, {
271
- header: `Awaiting review (${visibleQueue.length})`,
341
+ header: `Awaiting review (${total.queue}${more.queue ? '+' : ''})`,
272
342
  help: pluginDocsHelp('manifestAndEnvs', {
273
343
  anchor: '#review--trust-lifecycle',
274
344
  excerpt: 'Submissions waiting on a staff verdict. Open one to read its manifest, verifier findings and act.'
@@ -285,31 +355,34 @@ const STATUS_FILTERS = [
285
355
  },
286
356
  children: 'Submitted and In review are not installable by anyone.'
287
357
  }),
288
- visibleQueue.length ? /*#__PURE__*/ _jsx(Stack, {
289
- children: visibleQueue.map((entry)=>row(entry.listingId, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
290
- children: [
291
- /*#__PURE__*/ _jsx(Chip, {
292
- size: "small",
293
- color: reviewStatusMeaning(entry.reviewStatus).color,
294
- label: reviewStatusMeaning(entry.reviewStatus).label
295
- }),
296
- /*#__PURE__*/ _jsx(Chip, {
297
- size: "small",
298
- variant: "outlined",
299
- label: `v${entry.version || '—'}`
300
- }),
301
- entry.private ? /*#__PURE__*/ _jsx(Chip, {
302
- size: "small",
303
- variant: "outlined",
304
- label: "Private"
305
- }) : null,
306
- entry.license || entry.private ? null : /*#__PURE__*/ _jsx(Chip, {
307
- size: "small",
308
- color: "warning",
309
- label: "No license"
310
- })
311
- ]
312
- }), entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free'))
358
+ visibleQueue.length ? /*#__PURE__*/ _jsxs(Stack, {
359
+ children: [
360
+ visibleQueue.map((entry)=>row(entry.listingId, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
361
+ children: [
362
+ /*#__PURE__*/ _jsx(Chip, {
363
+ size: "small",
364
+ color: reviewStatusMeaning(entry.reviewStatus).color,
365
+ label: reviewStatusMeaning(entry.reviewStatus).label
366
+ }),
367
+ /*#__PURE__*/ _jsx(Chip, {
368
+ size: "small",
369
+ variant: "outlined",
370
+ label: `v${entry.version || '—'}`
371
+ }),
372
+ entry.private ? /*#__PURE__*/ _jsx(Chip, {
373
+ size: "small",
374
+ variant: "outlined",
375
+ label: "Private"
376
+ }) : null,
377
+ entry.license || entry.private ? null : /*#__PURE__*/ _jsx(Chip, {
378
+ size: "small",
379
+ color: "warning",
380
+ label: "No license"
381
+ })
382
+ ]
383
+ }), entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free')),
384
+ pager('queue', visibleQueue.length)
385
+ ]
313
386
  }) : /*#__PURE__*/ _jsx(Alert, {
314
387
  severity: filtering ? 'info' : 'success',
315
388
  children: filtering ? 'No submissions match this filter.' : 'No plugin submissions waiting for review.'
@@ -317,7 +390,7 @@ const STATUS_FILTERS = [
317
390
  ]
318
391
  }),
319
392
  /*#__PURE__*/ _jsxs(CardDisplay, {
320
- header: `Listed plugins (${visibleListed.length})`,
393
+ header: `Listed plugins (${total.listed}${more.listed ? '+' : ''})`,
321
394
  help: pluginDocsHelp('publisherHandbook', {
322
395
  anchor: '#review-what-happens-after-you-publish',
323
396
  excerpt: 'Listings already installable. Listing is the step that makes a ' + 'version reachable from Browse.'
@@ -337,36 +410,39 @@ const STATUS_FILTERS = [
337
410
  // it would misdescribe.
338
411
  'Rows marked Private are the exception: approved and ' + 'live, but only for the org that published them.'
339
412
  }),
340
- visibleListed.length ? /*#__PURE__*/ _jsx(Stack, {
341
- children: visibleListed.map((entry)=>row(entry.listingId, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
342
- children: [
343
- /*#__PURE__*/ _jsx(Chip, {
344
- size: "small",
345
- color: reviewStatusMeaning(entry.reviewStatus).color,
346
- label: reviewStatusMeaning(entry.reviewStatus).label
347
- }),
348
- /*#__PURE__*/ _jsx(Chip, {
349
- size: "small",
350
- variant: "outlined",
351
- label: `v${entry.latestVersion || '—'}`
352
- }),
353
- entry.private ? /*#__PURE__*/ _jsx(Chip, {
354
- size: "small",
355
- variant: "outlined",
356
- label: "Private"
357
- }) : null,
358
- entry.realmVersions ? /*#__PURE__*/ _jsx(Chip, {
359
- size: "small",
360
- color: "success",
361
- label: `${entry.realmVersions} realm-trusted`
362
- }) : null,
363
- entry.hidden ? /*#__PURE__*/ _jsx(Chip, {
364
- size: "small",
365
- color: "error",
366
- label: "Taken down"
367
- }) : null
368
- ]
369
- }), `${entry.versionCount} version${entry.versionCount === 1 ? '' : 's'}${entry.hidden && entry.hiddenReason ? ` · ${entry.hiddenReason}` : ''}`))
413
+ visibleListed.length ? /*#__PURE__*/ _jsxs(Stack, {
414
+ children: [
415
+ visibleListed.map((entry)=>row(entry.listingId, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
416
+ children: [
417
+ /*#__PURE__*/ _jsx(Chip, {
418
+ size: "small",
419
+ color: reviewStatusMeaning(entry.reviewStatus).color,
420
+ label: reviewStatusMeaning(entry.reviewStatus).label
421
+ }),
422
+ /*#__PURE__*/ _jsx(Chip, {
423
+ size: "small",
424
+ variant: "outlined",
425
+ label: `v${entry.latestVersion || '—'}`
426
+ }),
427
+ entry.private ? /*#__PURE__*/ _jsx(Chip, {
428
+ size: "small",
429
+ variant: "outlined",
430
+ label: "Private"
431
+ }) : null,
432
+ entry.realmVersions ? /*#__PURE__*/ _jsx(Chip, {
433
+ size: "small",
434
+ color: "success",
435
+ label: `${entry.realmVersions} realm-trusted`
436
+ }) : null,
437
+ entry.hidden ? /*#__PURE__*/ _jsx(Chip, {
438
+ size: "small",
439
+ color: "error",
440
+ label: "Taken down"
441
+ }) : null
442
+ ]
443
+ }), `${entry.versionCount} version${entry.versionCount === 1 ? '' : 's'}${entry.hidden && entry.hiddenReason ? ` · ${entry.hiddenReason}` : ''}`)),
444
+ pager('listed', visibleListed.length)
445
+ ]
370
446
  }) : /*#__PURE__*/ _jsx(Alert, {
371
447
  severity: "info",
372
448
  children: filtering ? 'No listed plugins match this filter.' : 'No listed plugins yet.'
@@ -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 { ICON_VARIANT_SYMBOL_FLAG } from '@aglyn/shared-data-enums'\nimport { PageHeaderHelp } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\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'\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/**\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 */\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 [status, setStatus] = useState('all')\n\n const refresh = useCallback(async () => {\n const response = await authorizedFetch(user, '/api/marketplace/admin/reviews')\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 } 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])\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 // One predicate for both sections, so a search never means two things.\n const matches = useCallback(\n (row: { displayName: string; listingId: string; profileId: string; reviewStatus: string; hidden: boolean }) => {\n const term = search.trim().toLowerCase()\n if (\n term &&\n ![row.displayName, row.listingId, publisherName(row.profileId)].some(\n (field) => String(field).toLowerCase().includes(term),\n )\n ) {\n return false\n }\n if (status === 'all') return true\n if (status === 'hidden') return row.hidden\n return row.reviewStatus === status\n },\n [search, status, publisherName],\n )\n\n const visibleQueue = useMemo(() => queue.filter(matches), [queue, matches])\n const visibleListed = useMemo(() => listed.filter(matches), [listed, matches])\n const visibleVerification = useMemo(\n () => verificationRequests.filter(matches),\n [verificationRequests, matches],\n )\n const filtering = search.trim().length > 0 || status !== 'all'\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 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 direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n placeholder=\"Search name, publisher or listing id\"\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\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 (${visibleVerification.length})`}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#asking-to-be-verified',\n excerpt:\n 'Publishers who asked to be VERIFIED — a claim about who they are, ' +\n 'separate from reviewing any one version.',\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 </Stack>\n </CardDisplay>\n ) : null}\n <CardDisplay\n header={`Awaiting review (${visibleQueue.length})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#review--trust-lifecycle',\n excerpt:\n 'Submissions waiting on a staff verdict. Open one to read its manifest, verifier findings and act.',\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 </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 (${visibleListed.length})`}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#review-what-happens-after-you-publish',\n excerpt:\n 'Listings already installable. Listing is the step that makes a ' +\n 'version reachable from Browse.',\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 </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","Alert","Chip","MenuItem","Skeleton","Stack","TextField","Typography","useCallback","useEffect","useMemo","useState","useUser","authorizedFetch","pluginDocsHelp","reviewStatusMeaning","STATUS_FILTERS","value","label","PluginReviewsQueue","basePath","data","user","queue","setQueue","listed","setListed","verificationRequests","setVerificationRequests","publishers","setPublishers","loaded","setLoaded","loadError","setLoadError","search","setSearch","status","setStatus","refresh","response","payload","json","catch","ok","error","publisherName","profileId","matches","row","term","trim","toLowerCase","displayName","listingId","some","field","String","includes","hidden","reviewStatus","visibleQueue","filter","visibleListed","visibleVerification","filtering","length","key","name","chips","caption","spacing","sx","py","borderBottom","borderColor","direction","alignItems","flexWrap","href","encodeURIComponent","variant","component","color","topic","anchor","size","placeholder","onChange","event","target","minWidth","select","map","option","severity","height","header","help","excerpt","contentGutterX","contentGutterY","display","mb","entry","version","private","license","priceUsd","latestVersion","realmVersions","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 { 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 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 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('publisherHandbook', {\n anchor: '#asking-to-be-verified',\n excerpt:\n 'Publishers who asked to be VERIFIED — a claim about who they are, ' +\n 'separate from reviewing any one version.',\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--trust-lifecycle',\n excerpt:\n 'Submissions waiting on a staff verdict. Open one to read its manifest, verifier findings and act.',\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('publisherHandbook', {\n anchor: '#review-what-happens-after-you-publish',\n excerpt:\n 'Listings already installable. Listing is the step that makes a ' +\n 'version reachable from Browse.',\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","direction","alignItems","flexWrap","href","encodeURIComponent","variant","component","color","topic","anchor","size","placeholder","onChange","event","target","minWidth","select","map","severity","height","header","help","excerpt","contentGutterX","contentGutterY","display","mb","entry","displayName","version","private","license","priceUsd","latestVersion","realmVersions","hidden","versionCount","hiddenReason"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,cAAc,QAAQ,eAAc;AAC7C,SAASC,OAAO,EAAEC,WAAW,QAAQ,uBAAsB;AAC3D,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,OAAOC,oBACLC,iBAAiB,QACZ,+DAA8D;AAErE,SACEC,KAAK,EACLC,IAAI,EACJC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACjE,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,cAAc,QAAQ,eAAc;AAC7C,SAASC,mBAAmB,QAAQ,mCAA+B;AACnE,SACEC,kBAAkB,QAEb,4BAAwB;AAgC/B,MAAMC,iBAAiB;IACrB;QAAEC,OAAO;QAAOC,OAAO;IAAe;IACtC;QAAED,OAAO;QAAaC,OAAO;IAAY;IACzC;QAAED,OAAO;QAAaC,OAAO;IAAY;IACzC;QAAED,OAAO;QAAUC,OAAO;IAAS;IACnC;QAAED,OAAO;QAAYC,OAAO;IAAW;IACvC;QAAED,OAAO;QAAUC,OAAO;IAAa;CACxC;AAED,+CAA+C,GAC/C,MAAMC,gBAAkD;IACtDC,cAAc;IACdC,WAAW;AACb;AACA,MAAMC,gBAAgB;IACpBF,cAAcJ,eAAeO,MAAM,CACjC,CAACC,SAAWA,OAAOP,KAAK,KAAK,SAASO,OAAOP,KAAK,KAAK;AAE3D;AAEA,mCAAmC,GACnC,MAAMQ,aAAa;AACnB,2EAA2E,GAC3E,MAAMC,mBAAmB;AAEzB,MAAMC,UAA+C;IACnDC,OAAO;IACPC,QAAQ;IACRC,cAAc;AAChB;AACA,MAAMC,cAAkD;IACtDH,OAAO;IACPC,QAAQ;IACRC,cAAc;AAChB;AAEA;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASE,mBAAmB,EAAEC,QAAQ,EAAwB;IACnE,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGxB;IACvB,MAAM,CAACiB,OAAOQ,SAAS,GAAG1B,SAAqB,EAAE;IACjD,MAAM,CAACmB,QAAQQ,UAAU,GAAG3B,SAAsB,EAAE;IACpD,MAAM,CAAC4B,sBAAsBC,wBAAwB,GAAG7B,SACtD,EAAE;IAEJ,MAAM,CAAC8B,YAAYC,cAAc,GAAG/B,SAAiC,CAAC;IACtE,MAAM,CAACgC,QAAQC,UAAU,GAAGjC,SAAS;IACrC,MAAM,CAACkC,WAAWC,aAAa,GAAGnC,SAAwB;IAC1D,MAAM,CAACoC,QAAQC,UAAU,GAAGrC,SAAS;IACrC,MAAM,CAACsC,OAAOC,SAAS,GAAGvC,SAAS;IACnC,MAAM,CAACwC,QAAQC,UAAU,GAAGzC,SAAS;IACrC,MAAM,CAAC0C,OAAOC,SAAS,GACrB3C,SAA6CqB;IAC/C,sEAAsE,GACtE,MAAMuB,QAAQ7B,aAAc8B,CAAAA,KAAKC,GAAG,IAAIC,OAAOC,MAAM,CAACN,UAAU,CAAA;IAChE,MAAM,CAACO,MAAMC,QAAQ,GAAGlD,SAA8CiB;IACtE,MAAM,CAACkC,SAASC,WAAW,GAAGpD,SAE5B,EAAE;IACJ,MAAM,CAACqD,SAASC,WAAW,GAAGtD,SAAmB,EAAE;IAEnD,8DAA8D;IAC9DF,UAAU;QACR,MAAMyD,QAAQC,WAAW,IAAMjB,SAASH,OAAOqB,IAAI,KAAKzC;QACxD,OAAO,IAAM0C,aAAaH;IAC5B,GAAG;QAACnB;KAAO;IACX,6CAA6C;IAC7CtC,UAAU;QACR6C,SAAStB;IACX,GAAG;QAACiB;QAAOE;KAAO;IAElB,MAAMmB,UAAU9D,YAAY;QAC1B,MAAM+D,SAAS,IAAIC;QACnB,IAAIvB,OAAOsB,OAAOE,GAAG,CAAC,KAAKxB;QAC3B,IAAIE,WAAW,OAAOoB,OAAOE,GAAG,CAAC,UAAUtB;QAC3CoB,OAAOE,GAAG,CAAC,SAASC,OAAOnB;QAC3B,MAAMoB,SAASJ,OAAOK,QAAQ;QAC9B,MAAMC,WAAW,MAAMhE,gBACrBuB,MACA,CAAC,8BAA8B,EAAEuC,SAAS,CAAC,CAAC,EAAEA,QAAQ,GAAG,IAAI;QAE/D,MAAMG,UAAU,MAAMD,SAASE,IAAI,GAAGC,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;QACpD,IAAIH,SAASI,EAAE,EAAE;;YACfnC,aAAa;YACbT,iBAASyC,2BAAAA,QAASjD,KAAK,mBAAI,EAAE;YAC7BS,mBAAUwC,2BAAAA,QAAShD,MAAM,oBAAI,EAAE;YAC/BU,iCAAwBsC,2BAAAA,QAASvC,oBAAoB,oBAAI,EAAE;YAC3DG,uBAAcoC,2BAAAA,QAASrC,UAAU,oBAAI,CAAC;YACtCoB,QAAQ,aAAKjC,kBAAakD,2BAAAA,QAASlB,IAAI,oBAAI,CAAC;YAC5CG,oBAAWe,2BAAAA,QAAShB,OAAO,oBAAI,EAAE;YACjCG,oBAAWa,2BAAAA,QAASd,OAAO,oBAAI,EAAE;QACnC,OAAO;;YACL,qEAAqE;YACrE,oEAAoE;YACpElB,sBACEgC,2BAAAA,QAASI,KAAK,oBAAI,CAAC,0BAA0B,EAAEL,SAAS1B,MAAM,CAAC,CAAC,CAAC;QAErE;QACAP,UAAU;IACZ,GAAG;QAACR;QAAMa;QAAOE;QAAQI;KAAM;IAE/B9C,UAAU;QACR,IAAI2B,MAAM,KAAKkC;IACjB,GAAG;QAAClC;QAAMkC;KAAQ;IAElB,MAAMa,gBAAgB3E,YACpB,CAAC4E;YAAsB3C;gBAAAA,wBAAAA,UAAU,CAAC2C,UAAU,YAArB3C,wBAAyB2C;OAChD;QAAC3C;KAAW;IAGd,uEAAuE;IACvE,+DAA+D;IAC/D,MAAM4C,SAAS,CAAKC,SAA6BC,OAC/CA,KAAKC,KAAK,CAACnC,KAAK,CAACiC,QAAQ,GAAG5D,YAAY,AAAC2B,CAAAA,KAAK,CAACiC,QAAQ,GAAG,CAAA,IAAK5D;IACjE,MAAM+D,eAAeJ,OAAO,SAASxD;IACrC,MAAM6D,gBAAgBL,OAAO,UAAUvD;IACvC,MAAM6D,sBAAsBN,OAAO,gBAAgB9C;IACnD,MAAMqD,QAA4C;QAChD/D,OAAOA,MAAMgE,MAAM;QACnB/D,QAAQA,OAAO+D,MAAM;QACrB9D,cAAcQ,qBAAqBsD,MAAM;IAC3C;IACA,MAAMC,YAAY7C,MAAM4C,MAAM,GAAG,KAAK1C,WAAW;IACjD,MAAM4C,WAAWrF,QACf,IACEV,kBAAkB8D,SAAS;YACzBkC,QAAQhF,mBAAmBgF,MAAM;YACjCC,SAAS7E;YACT8E,SAAS3E;QACX,IACF;QAACuC;KAAQ;IAEX,MAAMqC,QAAQ,CAACb,SAA6Bc;QAC1C,MAAMC,UACJT,KAAK,CAACN,QAAQ,GAAG,AAACjC,CAAAA,KAAK,CAACiC,QAAQ,GAAG,CAAA,IAAK5D,cAAckC,IAAI,CAAC0B,QAAQ;QACrE,OAAOjC,KAAK,CAACiC,QAAQ,GAAG,KAAKe,wBAC3B,KAACvG;YACCwG,MAAMjD,KAAK,CAACiC,QAAQ;YACpBiB,UAAU7E;YACV8E,UAAUJ;YACVC,SAASA;YACTI,cAAc,CAACH,OACbhD,SAAS,CAACoD,WAAc,aAAKA;wBAAU,CAACpB,QAAQ,EAAEgB;;aAGpD;IACN;IAEA,MAAMK,MAAM,CACVC,KACAC,WACAC,MACA1B,WACA2B,OACAC,wBAEA,MAAC3G;YAEC4G,SAAS;YACTC,IAAI;gBACFC,IAAI;gBACJC,cAAc;gBACdC,aAAa;gBACb,kBAAkB;oBAAED,cAAc;gBAAE;YACtC;;8BAEA,MAAC/G;oBACCiH,WAAU;oBACVL,SAAS;oBACTC,IAAI;wBAAEK,YAAY;wBAAUC,UAAU;oBAAO;;sCAE7C,KAAC5H;4BAAQ6H,MAAM,GAAGvF,SAAS,CAAC,EAAEwF,mBAAmBb,YAAY;sCAC3D,cAAA,KAACtG;gCAAWoH,SAAQ;gCAAYC,WAAU;0CACvCd;;;wBAGJC;;;8BAEH,KAACxG;oBAAWoH,SAAQ;oBAAUE,OAAM;8BACjC,GAAG1C,cAAcC,WAAW,GAAG,EAAE4B,SAAS;;;WAtBxCJ;IA2BT,qBACE;;0BAME,KAACjH;gBAAemI,OAAM;gBAAeC,QAAO;;0BASxC,MAAC1H;gBAAM4G,SAAS;;kCACd,MAAC5G;wBACCiH,WAAU;wBACVL,SAAS;wBACTC,IAAI;4BAAEK,YAAY;4BAAUC,UAAU;wBAAO;;0CAE7C,KAAClH;gCACC0H,MAAK;gCACLC,aAAY;gCACZ/G,OAAO6B;gCACPmF,UAAU,CAACC,QAAUnF,UAAUmF,MAAMC,MAAM,CAAClH,KAAK;gCACjDgG,IAAI;oCAAEmB,UAAU;gCAAI;;0CAEtB,KAAC/H;gCACC0H,MAAK;gCACLM,MAAM;gCACNpH,OAAOiC;gCACP+E,UAAU,CAACC,QAAU/E,UAAU+E,MAAMC,MAAM,CAAClH,KAAK;gCACjDgG,IAAI;oCAAEmB,UAAU;gCAAI;0CAEnBpH,eAAesH,GAAG,CAAC,CAAC9G,uBACnB,KAACtB;wCAA4Be,OAAOO,OAAOP,KAAK;kDAC7CO,OAAON,KAAK;uCADAM,OAAOP,KAAK;;;;oBAOhC2B,0BACC,KAAC5C;wBAAMuI,UAAS;kCAAS3F;yBACvB;kCACJ,KAAC9C;wBAAiB+D,SAASiC;wBAAU/B,SAASA;;oBAE7C,CAACrB,uBACA,MAACtC;wBAAM4G,SAAS;;0CACd,KAAC7G;gCAASuH,SAAQ;gCAAUc,QAAQ;;0CACpC,KAACrI;gCAASuH,SAAQ;gCAAUc,QAAQ;;;uCAGtC;;4BASG9C,oBAAoBE,MAAM,iBACzB,MAAChG;gCACC6I,QAAQ,CAAC,wBAAwB,EAAE9C,MAAM7D,YAAY,GAAG6B,KAAK7B,YAAY,GAAG,MAAM,GAAG,CAAC,CAAC;gCACvF4G,MAAM7H,eAAe,qBAAqB;oCACxCiH,QAAQ;oCACRa,SACE,uEACA;gCACJ;gCACAC,cAAc;gCACdC,cAAc;;kDAEd,KAACvI;wCACCoH,SAAQ;wCACRE,OAAM;wCACNX,IAAI;4CAAE6B,SAAS;4CAASC,IAAI;wCAAE;kDAE7B,uDACC,wDACA;;kDAEJ,MAAC3I;;4CACEsF,oBAAoB4C,GAAG,CAAC,CAACU,QACxBtC,IACE,CAAC,OAAO,EAAEsC,MAAMpC,SAAS,EAAE,EAC3BoC,MAAMpC,SAAS,EACfoC,MAAMC,WAAW,EACjBD,MAAM7D,SAAS,gBACf;;sEACE,KAAClF;4DACC8H,MAAK;4DACLH,OAAM;4DACN1G,OAAM;;sEAER,KAACjB;4DACC8H,MAAK;4DACLH,OAAO9G,oBAAoBkI,MAAM5H,YAAY,EAAEwG,KAAK;4DACpD1G,OAAOJ,oBAAoBkI,MAAM5H,YAAY,EAAEF,KAAK;;sEAEtD,KAACjB;4DACC8H,MAAK;4DACLL,SAAQ;4DACRxG,OAAO,CAAC,CAAC,EAAE8H,MAAME,OAAO,IAAI,KAAK;;;oDAGrC;4CAGHhD,MAAM,gBAAgBR,oBAAoBE,MAAM;;;;iCAGnD;0CACJ,MAAChG;gCACC6I,QAAQ,CAAC,iBAAiB,EAAE9C,MAAM/D,KAAK,GAAG+B,KAAK/B,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;gCAClE8G,MAAM7H,eAAe,mBAAmB;oCACtCiH,QAAQ;oCACRa,SACE;gCACJ;gCACAC,cAAc;gCACdC,cAAc;;kDAEd,KAACvI;wCACCoH,SAAQ;wCACRE,OAAM;wCACNX,IAAI;4CAAE6B,SAAS;4CAASC,IAAI;wCAAE;kDAE7B;;oCAEFvD,aAAaI,MAAM,iBAClB,MAACxF;;4CACEoF,aAAa8C,GAAG,CAAC,CAACU,QACjBtC,IACEsC,MAAMpC,SAAS,EACfoC,MAAMpC,SAAS,EACfoC,MAAMC,WAAW,EACjBD,MAAM7D,SAAS,gBACf;;sEACE,KAAClF;4DACC8H,MAAK;4DACLH,OAAO9G,oBAAoBkI,MAAM5H,YAAY,EAAEwG,KAAK;4DACpD1G,OAAOJ,oBAAoBkI,MAAM5H,YAAY,EAAEF,KAAK;;sEAEtD,KAACjB;4DACC8H,MAAK;4DACLL,SAAQ;4DACRxG,OAAO,CAAC,CAAC,EAAE8H,MAAME,OAAO,IAAI,KAAK;;wDAMlCF,MAAMG,OAAO,iBACZ,KAAClJ;4DACC8H,MAAK;4DACLL,SAAQ;4DACRxG,OAAM;6DAEN;wDAGH8H,MAAMI,OAAO,IAAIJ,MAAMG,OAAO,GAAG,qBAChC,KAAClJ;4DACC8H,MAAK;4DACLH,OAAM;4DACN1G,OAAM;;;oDAIZ8H,MAAMK,QAAQ,GAAG,IAAI,CAAC,CAAC,EAAEL,MAAMK,QAAQ,EAAE,GAAG;4CAG/CnD,MAAM,SAASV,aAAaI,MAAM;;uDAGrC,KAAC5F;wCAAMuI,UAAU1C,YAAY,SAAS;kDACnCA,YACG,sCACA;;;;0CAKV,MAACjG;gCACC6I,QAAQ,CAAC,gBAAgB,EAAE9C,MAAM9D,MAAM,GAAG8B,KAAK9B,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC;gCACnE6G,MAAM7H,eAAe,qBAAqB;oCACxCiH,QAAQ;oCACRa,SACE,oEACA;gCACJ;gCACAC,cAAc;gCACdC,cAAc;;kDAKd,KAACvI;wCACCoH,SAAQ;wCACRE,OAAM;wCACNX,IAAI;4CAAE6B,SAAS;4CAASC,IAAI;wCAAE;kDAE7B,4DACC,yDACA,0DACA,gDAAgD;wCAChD,oDAAoD;wCACpD,wBAAwB;wCACxB,yDACA;;oCAEHtD,cAAcG,MAAM,iBACnB,MAACxF;;4CACEqF,cAAc6C,GAAG,CAAC,CAACU,QAClBtC,IACEsC,MAAMpC,SAAS,EACfoC,MAAMpC,SAAS,EACfoC,MAAMC,WAAW,EACjBD,MAAM7D,SAAS,gBACf;;sEACE,KAAClF;4DACC8H,MAAK;4DACLH,OAAO9G,oBAAoBkI,MAAM5H,YAAY,EAAEwG,KAAK;4DACpD1G,OAAOJ,oBAAoBkI,MAAM5H,YAAY,EAAEF,KAAK;;sEAEtD,KAACjB;4DACC8H,MAAK;4DACLL,SAAQ;4DACRxG,OAAO,CAAC,CAAC,EAAE8H,MAAMM,aAAa,IAAI,KAAK;;wDAExCN,MAAMG,OAAO,iBACZ,KAAClJ;4DACC8H,MAAK;4DACLL,SAAQ;4DACRxG,OAAM;6DAEN;wDACH8H,MAAMO,aAAa,iBAClB,KAACtJ;4DACC8H,MAAK;4DACLH,OAAM;4DACN1G,OAAO,GAAG8H,MAAMO,aAAa,CAAC,cAAc,CAAC;6DAE7C;wDACHP,MAAMQ,MAAM,iBACX,KAACvJ;4DACC8H,MAAK;4DACLH,OAAM;4DACN1G,OAAM;6DAEN;;oDAEN,GAAG8H,MAAMS,YAAY,CAAC,QAAQ,EAC5BT,MAAMS,YAAY,KAAK,IAAI,KAAK,MAC/BT,MAAMQ,MAAM,IAAIR,MAAMU,YAAY,GAAG,CAAC,GAAG,EAAEV,MAAMU,YAAY,EAAE,GAAG,IAAI;4CAG5ExD,MAAM,UAAUT,cAAcG,MAAM;;uDAGvC,KAAC5F;wCAAMuI,UAAS;kDACb1C,YACG,yCACA;;;;;;;;;;AAS1B;AAEA,eAAe7D,mBAAkB"}
@@ -0,0 +1,34 @@
1
+ import { type ListQueryRefusal } from '@aglyn/shared-ui-jsx/const/list-query-plan';
2
+ export interface ListingNameLookup {
3
+ /**
4
+ * The listings a search names, for a `listingId in [...]` clause; `null`
5
+ * when nothing is searched, so the list is not narrowed at all.
6
+ */
7
+ ids: string[] | null;
8
+ /** Still asking which listings the word names. */
9
+ pending: boolean;
10
+ /** The search, refused — too many listings share the word. */
11
+ refused: ListQueryRefusal[];
12
+ /** Said about what was searched, e.g. one word of two. */
13
+ notices: string[];
14
+ }
15
+ /**
16
+ * The listings whose NAME holds a typed word (AGL-3321).
17
+ *
18
+ * A license row names its listing by id, and the name lives on the listing,
19
+ * where the publisher can change it. So a license search asks the listings
20
+ * first — `nameTokens array-contains` the word, the key every listing writer
21
+ * stamps — and the license query then asks for those listing ids. Nothing is
22
+ * matched over rows already loaded.
23
+ *
24
+ * Firestore takes at most thirty values in one `in`, so a word naming more
25
+ * than thirty listings is refused by name rather than answered with thirty of
26
+ * them.
27
+ */
28
+ export declare function useListingNameLookup(words: readonly string[]): ListingNameLookup;
29
+ /**
30
+ * Listing id → display name, for the rows on screen: read BY ID, thirty at a
31
+ * time, so a license always names its listing however large the catalog is.
32
+ * A listing that cannot be read keeps its id as its label.
33
+ */
34
+ export declare function useListingNames(ids: readonly string[]): Record<string, string>;