@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/site-starter-review-detail.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\n'use client'\n\nimport { pluginDocsHelp, type ConsoleStaffPagePaths } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { SITE_FONT_PAIRINGS } from '@aglyn/aglyn/app-utils/site-font-pairings'\nimport { siteLiveUrlForSlug } from '@aglyn/aglyn/app-utils/site-live-url'\nimport { SITE_STARTER_BRAND_KEYS } from '@aglyn/aglyn/app-utils/site-starter-theme'\nimport { siteStyleOf } from '@aglyn/aglyn/app-utils/site-styles'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Box,\n Button,\n Checkbox,\n Chip,\n Divider,\n FormControlLabel,\n Link as MuiLink,\n MenuItem,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { useState } from 'react'\nimport { orgMarketplacePath, publisherPath } from '../model/marketplace-paths'\nimport {\n rejectionCategoriesFor,\n rejectionHeadline,\n rejectionInputError,\n pluginRejectionCategory,\n} from '../model/plugin-rejection-categories'\nimport { SITE_STARTER_REVIEW_CHECKLIST } from '../model/plugin-review-checklist'\nimport { starterReviewStatusMeaning } from '../model/plugin-review-status'\n\n/** One site starter, as the review route answers for its page (AGL-4049). */\nexport interface SiteStarterReviewDetail {\n artifactType: 'siteStarter'\n listingId: string\n displayName: string\n description: string\n category: string | null\n previewImageUrl: string | null\n priceUsd: number\n official: boolean\n reviewStatus: string\n rejectionReason: string\n rejectionCategory: string\n /** The version the last List or Reject was given on. */\n reviewedVersion: string | null\n hidden: boolean\n hiddenReason: string\n unpublished: boolean\n latestVersion: string\n /** The version the pages, theme and checklist below are read from. */\n reviewVersion: string\n mode: 'base' | 'ai'\n facets: { kind: string | null; base: string | null; fonts: string | null; pages: number; mode: string }\n pages: Array<{ name: string; slug: string | null; published: boolean }>\n layout: { name: string } | null\n theme:\n | {\n kind: 'builtin'\n base: string\n fonts: string | null\n brand: { light: Record<string, string>; dark?: Record<string, string> }\n }\n | { kind: 'full' }\n | null\n publisherId: string\n publisherName: string\n publisherSlug: string | null\n publisherAgreement: { version: string | null; required: string; state: 'none' | 'outdated' | 'current' }\n sourceSite: { hostId: string; name: string | null; subdomain: string | null; cname: string | null } | null\n versions: Array<{ version: string; publishedAt: string | null; pages: number }>\n checklist: Record<string, { by: string | null }>\n checklistOutstanding: string[]\n}\n\nexport interface SiteStarterReviewBodyProps {\n detail: SiteStarterReviewDetail\n busy: boolean\n /** Sends one action to the review route, then reads the page again. */\n post: (payload: Record<string, unknown>, success: string) => Promise<void>\n /** Reads the page for another version. */\n onSelectVersion: (version: string) => void\n staffPaths?: ConsoleStaffPagePaths\n}\n\nconst plural = (count: number, one: string) => `${count} ${one}${count === 1 ? '' : 's'}`\n\n/** A font pairing as a reviewer reads it: its heading face over its text face. */\nfunction fontsLabel(id: string | null): string {\n if (!id) return 'The base theme’s own fonts'\n const pairing = SITE_FONT_PAIRINGS.find((entry) => entry.id === id)\n return pairing ? `${pairing.heading.family} headings, ${pairing.body.family} text` : id\n}\n\n/** One brand color, drawn from the publisher's value with the theme's border around it. */\nfunction Swatch({ scheme, name, value }: { scheme: string; name: string; value: string }) {\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Box\n role=\"img\"\n aria-label={`${scheme} ${name} ${value}`}\n sx={{\n width: (theme) => theme.spacing(3),\n height: (theme) => theme.spacing(3),\n borderRadius: 1,\n border: 1,\n borderColor: 'divider',\n // The publisher's own color, as their starter stores it: data, not style.\n bgcolor: value,\n }}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">{`${name} ${value}`}</Typography>\n </Stack>\n )\n}\n\n/**\n * A site starter's review page (AGL-4049): what a site created from it gets,\n * the starter checklist, and its two verdicts.\n *\n * A starter is not code. Nothing on the plugin page — manifest, verifier,\n * per-version approval, realm trust — has anything to act on in it, so this\n * page reads what a person's new site is made of instead: every page, with a\n * link to it on the site it was published from, the layout they sit in, the\n * theme and its brand colors in both schemes, the picture and facets a search\n * shows. List waits for every checklist item on the newest version; Reject\n * takes one of a starter's own reasons, which its publisher is told.\n */\nexport function SiteStarterReviewBody({\n detail,\n busy,\n post,\n onSelectVersion,\n staffPaths,\n}: SiteStarterReviewBodyProps) {\n const [rejectCategory, setRejectCategory] = useState('')\n const [reason, setReason] = useState('')\n const [takedownReason, setTakedownReason] = useState('')\n\n const status = starterReviewStatusMeaning(detail.reviewStatus)\n const outstanding = detail.checklistOutstanding.length\n // List is a verdict on the newest version, so reading an older one holds it.\n const readingOlder = detail.reviewVersion !== detail.latestVersion\n const blocked = outstanding > 0 || readingOlder\n const newerSinceReview =\n Boolean(detail.reviewedVersion) && detail.reviewedVersion !== detail.latestVersion\n const rejectBlocked = rejectionInputError(rejectCategory, reason, 'siteStarter')\n const needsComment = Boolean(pluginRejectionCategory(rejectCategory)?.requiresComment)\n const style = siteStyleOf(detail.facets.kind)\n const site = detail.sourceSite\n const siteName = site?.name ?? 'the site it came from'\n const pageUrl = (slug: string) =>\n site?.subdomain || site?.cname\n ? siteLiveUrlForSlug({ subdomain: site.subdomain, cname: site.cname }, slug)\n : undefined\n const ticked = SITE_STARTER_REVIEW_CHECKLIST.filter((item) => detail.checklist?.[item.id]).length\n const blockedReason = readingOlder\n ? `List reviews the newest version, v${detail.latestVersion}. Pick it under Versions.`\n : `Complete the review checklist first — ${plural(outstanding, 'required item')} outstanding for v${detail.reviewVersion}`\n\n const checklistLink = (id: string) => {\n if (id === 'starter-pages' && site) {\n return (\n <AppLink href={buildRoute(Route.ADMIN_SITE_DETAIL, { hostId: site.hostId })}>\n {site.name ? `${site.name}’s staff page` : 'The site’s staff page'}\n </AppLink>\n )\n }\n if (id === 'publisher') {\n const href = staffPaths?.orgDetail(detail.publisherId)\n return href ? <AppLink href={href}>{`${detail.publisherName}’s staff page`}</AppLink> : null\n }\n return null\n }\n\n return (\n <Stack spacing={3}>\n <CardDisplay\n header=\"Status\"\n help={pluginDocsHelp('staffConsole', { anchor: '#plugin-review-starter-status' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Chip size=\"small\" color={status.color} label={status.label} />\n <Chip size=\"small\" variant=\"outlined\" label={`v${detail.latestVersion || '—'}`} />\n <Chip size=\"small\" variant=\"outlined\" label={detail.priceUsd > 0 ? `$${detail.priceUsd}` : 'Free'} />\n {detail.official ? <Chip size=\"small\" variant=\"outlined\" label=\"Official\" /> : null}\n {detail.hidden ? <Chip size=\"small\" color=\"error\" label=\"Taken down\" /> : null}\n {detail.unpublished ? (\n <Chip size=\"small\" color=\"warning\" label=\"Unpublished by the publisher\" />\n ) : null}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {status.meaning}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publisher: '}\n {detail.publisherSlug ? (\n <AppLink href={publisherPath(orgMarketplacePath(detail.publisherSlug), detail.publisherId)}>\n {detail.publisherName}\n </AppLink>\n ) : (\n detail.publisherName\n )}\n {` · ${detail.listingId}`}\n </Typography>\n {detail.publisherAgreement.state !== 'current' ? (\n <Alert severity=\"warning\">\n {detail.publisherAgreement.state === 'none'\n ? 'This publisher has not accepted the Marketplace Publisher Agreement.'\n : `This publisher accepted an older Marketplace Publisher Agreement (${detail.publisherAgreement.version}); ${detail.publisherAgreement.required} is in force.`}\n </Alert>\n ) : null}\n {newerSinceReview ? (\n <Alert severity=\"info\">\n {`v${detail.latestVersion} was published after the review of v${detail.reviewedVersion}. ` +\n 'A listed starter stays listed when it is published again: read the new version, and reject it or take it down if it no longer passes.'}\n </Alert>\n ) : null}\n {detail.rejectionReason || detail.rejectionCategory ? (\n <Alert severity=\"error\">\n {`Rejected: ${rejectionHeadline(detail.rejectionCategory, detail.rejectionReason)}${\n detail.rejectionReason && pluginRejectionCategory(detail.rejectionCategory)\n ? ` — ${detail.rejectionReason}`\n : ''\n }`}\n </Alert>\n ) : null}\n {detail.hidden ? (\n <Alert severity=\"error\">\n {detail.hiddenReason ? `Taken down: ${detail.hiddenReason}` : 'Taken down (no reason recorded)'}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header=\"Overview\"\n help={pluginDocsHelp('staffConsole', { anchor: '#plugin-review-starter-overview' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n {detail.previewImageUrl ? (\n <Box\n component=\"img\"\n src={detail.previewImageUrl}\n alt={`The picture ${detail.displayName} is shown with`}\n sx={{\n width: '100%',\n maxWidth: (theme) => theme.spacing(60),\n borderRadius: 1,\n border: 1,\n borderColor: 'divider',\n }}\n />\n ) : (\n <Alert severity=\"warning\">{'No picture: the starter shows without one in the marketplace.'}</Alert>\n )}\n <Typography variant=\"body2\">{detail.description || 'No description.'}</Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n {detail.category ? <Chip size=\"small\" label={detail.category} /> : null}\n {style ? <Chip size=\"small\" variant=\"outlined\" label={`Style: ${style.name}`} /> : null}\n <Chip size=\"small\" variant=\"outlined\" label={plural(detail.facets.pages, 'page')} />\n {detail.mode === 'ai' ? <Chip size=\"small\" variant=\"outlined\" label=\"AI starter\" /> : null}\n </Stack>\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header={`Pages (${detail.pages.length})`}\n help={pluginDocsHelp('staffConsole', { anchor: '#plugin-review-starter-pages' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`What a site created from v${detail.reviewVersion} starts with. Each published page links to the same page on ${siteName}, as that site is now.`}\n </Typography>\n <Stack divider={<Divider flexItem />} spacing={1}>\n {detail.pages.map((page, index) => {\n const href = page.published && page.slug ? pageUrl(page.slug) : undefined\n return (\n <Stack\n key={`${page.slug ?? ''}-${index}`}\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"body2\">{page.name}</Typography>\n {page.published && page.slug ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {page.slug}\n </Typography>\n ) : (\n <Chip size=\"small\" variant=\"outlined\" label=\"Not published\" />\n )}\n {href ? (\n <Typography variant=\"caption\">\n <MuiLink href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n {`Open on ${siteName} ↗`}\n </MuiLink>\n </Typography>\n ) : null}\n </Stack>\n )\n })}\n </Stack>\n <Divider />\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Shared layout'}</Typography>\n <Typography variant=\"body2\">{detail.layout?.name ?? 'None'}</Typography>\n </Stack>\n {site && !site.subdomain && !site.cname ? (\n <Alert severity=\"warning\">\n {'The site it was published from is gone, so its pages can only be read here.'}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header=\"Theme\"\n help={pluginDocsHelp('staffConsole', { anchor: '#plugin-review-starter-theme' })}\n contentGutterX\n contentGutterY\n >\n {detail.theme?.kind === 'builtin' ? (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\">{'Built-in theme with its brand colors'}</Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Base theme'}\n </Typography>\n <Chip size=\"small\" variant=\"outlined\" label={detail.theme.base} />\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {fontsLabel(detail.theme.fonts)}\n </Typography>\n {(['light', 'dark'] as const).map((scheme) => {\n const builtin = detail.theme?.kind === 'builtin' ? detail.theme : null\n const colors = scheme === 'dark' ? (builtin?.brand.dark ?? null) : (builtin?.brand.light ?? {})\n const label = scheme === 'dark' ? 'Dark' : 'Light'\n return (\n <Stack key={scheme} spacing={0.5}>\n <Typography variant=\"subtitle2\">{label}</Typography>\n {colors ? (\n <Stack useFlexGap direction=\"row\" spacing={2} sx={{ flexWrap: 'wrap' }}>\n {SITE_STARTER_BRAND_KEYS.filter((key) => colors[key]).map((key) => (\n <Swatch key={key} scheme={label} name={key} value={colors[key]} />\n ))}\n </Stack>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Uses the light colors.'}\n </Typography>\n )}\n </Stack>\n )\n })}\n </Stack>\n ) : detail.theme?.kind === 'full' ? (\n <Typography variant=\"body2\">\n {'The site’s own full theme. Read it on the pages, in light and in dark.'}\n </Typography>\n ) : (\n <Alert severity=\"warning\">{'No theme is stored with this version.'}</Alert>\n )}\n </CardDisplay>\n\n <CardDisplay\n header={`Review checklist — v${detail.reviewVersion} (${ticked}/${SITE_STARTER_REVIEW_CHECKLIST.length})`}\n help={pluginDocsHelp('staffConsole', { anchor: '#plugin-review-starter-checklist' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Every item is required before the starter is listed. Ticks are recorded against this version and start again when it is published again.'}\n </Typography>\n {outstanding ? (\n <Alert severity=\"info\">\n {`${plural(outstanding, 'required item')} outstanding — List stays blocked until they are done. Rejecting never needs the checklist.`}\n </Alert>\n ) : (\n <Alert severity=\"success\">{'Every required item is recorded for this version.'}</Alert>\n )}\n {SITE_STARTER_REVIEW_CHECKLIST.map((item) => {\n const link = checklistLink(item.id)\n return (\n <Stack key={item.id} spacing={0.25} sx={{ pl: 0.5 }}>\n <FormControlLabel\n control={\n <Checkbox\n size=\"small\"\n checked={Boolean(detail.checklist?.[item.id])}\n disabled={busy}\n onChange={(event) =>\n void post(\n {\n action: 'checklist',\n version: detail.reviewVersion,\n itemId: item.id,\n checked: event.target.checked,\n },\n event.target.checked ? 'Recorded' : 'Cleared',\n )\n }\n />\n }\n label={\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\">{item.label}</Typography>\n {item.required ? <Chip size=\"small\" label=\"Required\" /> : null}\n </Stack>\n }\n />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ pl: 4 }}>\n {item.detail}\n </Typography>\n {link ? (\n <Typography variant=\"caption\" sx={{ pl: 4 }}>\n {link}\n </Typography>\n ) : null}\n </Stack>\n )\n })}\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header=\"Review verdict\"\n help={pluginDocsHelp('staffConsole', { anchor: '#plugin-review-starter-verdict' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\">{'Currently:'}</Typography>\n <Chip size=\"small\" color={status.color} label={status.label} />\n {status.live ? (\n <Chip size=\"small\" color=\"warning\" label=\"Anyone can choose it\" />\n ) : (\n <Chip size=\"small\" variant=\"outlined\" label=\"Its publisher only\" />\n )}\n </Stack>\n {!status.live || newerSinceReview ? (\n <Stack spacing={0.5}>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Tooltip\n title={\n blocked\n ? blockedReason\n : 'Lists it in the marketplace, where anyone creating a site can choose it, and tells its publisher.'\n }\n >\n <span>\n <Button\n size=\"small\"\n color=\"warning\"\n variant=\"outlined\"\n disabled={busy || blocked}\n onClick={() => void post({ action: 'list' }, 'Listed — anyone creating a site can choose it')}\n >\n {'List in marketplace'}\n </Button>\n </span>\n </Tooltip>\n </Stack>\n {blocked ? (\n <Alert severity=\"info\" sx={{ mt: 0.5 }}>\n {readingOlder\n ? blockedReason\n : `Blocked: ${plural(outstanding, 'required checklist item')} outstanding for v${detail.reviewVersion}.`}\n </Alert>\n ) : null}\n </Stack>\n ) : null}\n <Divider />\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Reject'}</Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"Rejection reason\"\n value={rejectCategory}\n onChange={(event) => setRejectCategory(event.target.value)}\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n sx={{ minWidth: (theme) => theme.spacing(32) }}\n >\n <MenuItem value=\"\">{'Select a reason…'}</MenuItem>\n {rejectionCategoriesFor('siteStarter').map((entry) => (\n <MenuItem key={entry.id} value={entry.id}>\n {entry.label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n placeholder={needsComment ? 'Comment (required for “Other”)' : 'Comment (optional)'}\n value={reason}\n onChange={(event) => setReason(event.target.value)}\n sx={{ minWidth: (theme) => theme.spacing(32) }}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || Boolean(rejectBlocked)}\n onClick={() => void post({ action: 'reject', category: rejectCategory, reason }, 'Rejected')}\n >\n {'Reject'}\n </Button>\n </Stack>\n {rejectBlocked ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {rejectBlocked}\n </Typography>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Rejecting tells the publisher the reason and what to do about it, and takes a listed starter out of the marketplace. Sites already created from it keep their pages. Publishing it again sends it back here.'}\n </Typography>\n </Stack>\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header=\"Versions\"\n help={pluginDocsHelp('staffConsole', { anchor: '#plugin-review-starter-versions' })}\n contentGutterX\n contentGutterY\n >\n <Stack divider={<Divider flexItem />} spacing={1}>\n {detail.versions.map((entry) => (\n <Stack\n key={entry.version}\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"body2\">{`v${entry.version}`}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {[\n entry.publishedAt ? `Published ${new Date(entry.publishedAt).toLocaleDateString()}` : '',\n plural(entry.pages, 'page'),\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n {entry.version === detail.reviewedVersion ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Last reviewed\" />\n ) : null}\n {entry.version === detail.reviewVersion ? (\n <Chip size=\"small\" color=\"info\" label=\"Shown above\" />\n ) : (\n <Button size=\"small\" disabled={busy} onClick={() => onSelectVersion(entry.version)}>\n {'Read this version'}\n </Button>\n )}\n </Stack>\n ))}\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header=\"Danger zone\"\n help={pluginDocsHelp('staffConsole', { anchor: '#plugin-review-starter-danger-zone' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Taking a starter down takes it out of the marketplace and refuses every new site from it, whatever its review status. Sites already created from it keep their own copy. Restoring returns it to the status it had.'}\n </Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n {detail.hidden ? null : (\n <TextField\n size=\"small\"\n placeholder=\"Takedown reason\"\n value={takedownReason}\n onChange={(event) => setTakedownReason(event.target.value)}\n sx={{ minWidth: (theme) => theme.spacing(32) }}\n />\n )}\n <Button\n size=\"small\"\n variant={detail.hidden ? 'text' : 'outlined'}\n color={detail.hidden ? 'success' : 'error'}\n disabled={busy || (!detail.hidden && !takedownReason.trim())}\n onClick={() =>\n void post(\n { action: detail.hidden ? 'unhide' : 'hide', reason: takedownReason },\n detail.hidden ? `${detail.displayName} restored` : `${detail.displayName} taken down`,\n ).then(() => setTakedownReason(''))\n }\n >\n {detail.hidden ? 'Restore listing' : 'Take down'}\n </Button>\n </Stack>\n </Stack>\n </CardDisplay>\n </Stack>\n )\n}\n\nSiteStarterReviewBody.displayName = 'SiteStarterReviewBody'\n\nexport default SiteStarterReviewBody\n"],"names":["pluginDocsHelp","buildRoute","Route","SITE_FONT_PAIRINGS","siteLiveUrlForSlug","SITE_STARTER_BRAND_KEYS","siteStyleOf","AppLink","CardDisplay","Alert","Box","Button","Checkbox","Chip","Divider","FormControlLabel","Link","MuiLink","MenuItem","Stack","TextField","Tooltip","Typography","useState","orgMarketplacePath","publisherPath","rejectionCategoriesFor","rejectionHeadline","rejectionInputError","pluginRejectionCategory","SITE_STARTER_REVIEW_CHECKLIST","starterReviewStatusMeaning","plural","count","one","fontsLabel","id","pairing","find","entry","heading","family","body","Swatch","scheme","name","value","direction","spacing","sx","alignItems","role","aria-label","width","theme","height","borderRadius","border","borderColor","bgcolor","variant","color","SiteStarterReviewBody","detail","busy","post","onSelectVersion","staffPaths","rejectCategory","setRejectCategory","reason","setReason","takedownReason","setTakedownReason","status","reviewStatus","outstanding","checklistOutstanding","length","readingOlder","reviewVersion","latestVersion","blocked","newerSinceReview","Boolean","reviewedVersion","rejectBlocked","needsComment","requiresComment","style","facets","kind","site","sourceSite","siteName","pageUrl","slug","subdomain","cname","undefined","ticked","filter","item","checklist","blockedReason","checklistLink","href","ADMIN_SITE_DETAIL","hostId","orgDetail","publisherId","publisherName","header","help","anchor","contentGutterX","contentGutterY","useFlexGap","flexWrap","size","label","priceUsd","official","hidden","unpublished","meaning","publisherSlug","listingId","publisherAgreement","state","severity","version","required","rejectionReason","rejectionCategory","hiddenReason","previewImageUrl","component","src","alt","displayName","maxWidth","description","category","pages","mode","divider","flexItem","map","page","index","published","target","rel","layout","base","fonts","builtin","colors","brand","dark","light","key","link","pl","control","checked","disabled","onChange","event","action","itemId","live","title","span","onClick","mt","select","slotProps","displayEmpty","inputLabel","shrink","minWidth","placeholder","versions","publishedAt","Date","toLocaleDateString","join","trim","then"],"mappings":"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"}
@@ -24,6 +24,13 @@ import type { ConsoleHostStarterPickerZoneProps } from '@aglyn/aglyn/plugin-mana
24
24
  * instead. The dialog's own `selected` wins over the shelf's pick: it is how
25
25
  * a pick restored after paying is shown chosen, and how one the create
26
26
  * refused is cleared.
27
+ *
28
+ * An AI starter the workspace may use — free, owned, or just bought — has
29
+ * the answers it carries read once it is picked (AGL-4049 phase 4), and
30
+ * handed to the dialog with it (`onPick(pick, { answers })`), which goes on
31
+ * to the AI start's questions with them. A priced one not yet bought is
32
+ * never read: its answers are what it sells, and the door would ask payment
33
+ * for them. A door that still asks is told back to the dialog as unowned.
27
34
  */
28
35
  export declare function StarterPickerShelf(props: ConsoleHostStarterPickerZoneProps): import("react").JSX.Element;
29
36
  export default StarterPickerShelf;
@@ -22,6 +22,7 @@ import { mdiWeb } from "@aglyn/shared-data-mdi";
22
22
  import { OptionCardGrid } from "@aglyn/shared-ui-jsx/components/option-card-grid.component";
23
23
  import { Alert, Box, Button, Link, LinearProgress, Skeleton, Stack } from "@mui/material";
24
24
  import { useEffect, useMemo, useRef, useState } from "react";
25
+ import { useStarterAnswers } from "../hooks/use-starter-answers.js";
25
26
  import { useStarterPurchase } from "../hooks/use-starter-purchase.js";
26
27
  import { useStarterSearch } from "../hooks/use-starter-search.js";
27
28
  import { starterCardFacts, starterPriceLabel } from "../model/starter-query.js";
@@ -140,6 +141,13 @@ const narrowed = (filter)=>[
140
141
  * instead. The dialog's own `selected` wins over the shelf's pick: it is how
141
142
  * a pick restored after paying is shown chosen, and how one the create
142
143
  * refused is cleared.
144
+ *
145
+ * An AI starter the workspace may use — free, owned, or just bought — has
146
+ * the answers it carries read once it is picked (AGL-4049 phase 4), and
147
+ * handed to the dialog with it (`onPick(pick, { answers })`), which goes on
148
+ * to the AI start's questions with them. A priced one not yet bought is
149
+ * never read: its answers are what it sells, and the door would ask payment
150
+ * for them. A door that still asks is told back to the dialog as unowned.
143
151
  */ export function StarterPickerShelf(props) {
144
152
  var _ref, _found_rows_find;
145
153
  const { orgId, mode, search, filter, onPick, ready, selected } = props;
@@ -161,10 +169,13 @@ const narrowed = (filter)=>[
161
169
  const [shown, setShown] = useState(false);
162
170
  // Starters a purchase attempt found this workspace already owns.
163
171
  const [ownedHere, setOwnedHere] = useState(()=>new Set());
172
+ // AI starters whose answers the door still asked payment for (AGL-4049).
173
+ const [unpaidHere, setUnpaidHere] = useState(()=>new Set());
164
174
  const purchase = useStarterPurchase(orgId);
175
+ const starterAnswers = useStarterAnswers(orgId);
165
176
  const value = selected !== undefined ? (_ref = selected == null ? void 0 : selected.listingId) != null ? _ref : null : picked;
166
177
  const pickedRow = value ? (_found_rows_find = found.rows.find((row)=>row.listingId === value)) != null ? _found_rows_find : null : null;
167
- const ownedOverride = (row)=>ownedHere.has(row.listingId) ? true : (selected == null ? void 0 : selected.listingId) === row.listingId ? selected.owned : undefined;
178
+ const ownedOverride = (row)=>ownedHere.has(row.listingId) ? true : unpaidHere.has(row.listingId) ? false : (selected == null ? void 0 : selected.listingId) === row.listingId ? selected.owned : undefined;
168
179
  const refused = found.error !== null && REFUSED.has(found.error.status);
169
180
  // `null` until the first answer, which is the only one that may decide
170
181
  // there is nothing to show; after that only a refusal takes it back.
@@ -203,6 +214,51 @@ const narrowed = (filter)=>[
203
214
  restored,
204
215
  pickedRow
205
216
  ]);
217
+ // The picked AI starter's answers (AGL-4049), read once it is one the
218
+ // workspace may use; keyed on the listing and whether it is owned, so a
219
+ // purchase that makes it usable reads it, and nothing reads it twice.
220
+ const pickedOwned = pickedRow ? ownedOf(pickedRow, ownedOverride(pickedRow)) : false;
221
+ const answersKey = pickedRow && pickedRow.facets.mode === 'ai' && (pickedRow.priceUsd <= 0 || pickedOwned) ? `${pickedRow.listingId}:${pickedOwned}` : null;
222
+ const [reading, setReading] = useState(null);
223
+ const [readAttempt, setReadAttempt] = useState(0);
224
+ const askedAnswers = useRef(null);
225
+ useEffect(()=>{
226
+ if (!answersKey || !pickedRow || askedAnswers.current === answersKey) return;
227
+ askedAnswers.current = answersKey;
228
+ const row = pickedRow;
229
+ const override = ownedOverride(row);
230
+ const handed = override === undefined ? row : _extends({}, row, {
231
+ owned: override
232
+ });
233
+ setReading({
234
+ key: answersKey,
235
+ failed: null
236
+ });
237
+ void starterAnswers.read(row.listingId).then((outcome)=>{
238
+ if (askedAnswers.current !== answersKey) return;
239
+ if (outcome.status === 'failed') return setReading({
240
+ key: answersKey,
241
+ failed: outcome.message
242
+ });
243
+ setReading(null);
244
+ if (outcome.status === 'ready') return onPickRef.current(handed, {
245
+ answers: outcome.answers
246
+ });
247
+ setUnpaidHere((current)=>new Set(current).add(row.listingId));
248
+ onPickRef.current(_extends({}, row, {
249
+ owned: false
250
+ }));
251
+ });
252
+ // eslint-disable-next-line react-hooks/exhaustive-deps
253
+ }, [
254
+ answersKey,
255
+ readAttempt
256
+ ]);
257
+ const readingPicked = reading !== null && reading.key === answersKey ? reading : null;
258
+ const readAgain = ()=>{
259
+ askedAnswers.current = null;
260
+ setReadAttempt((count)=>count + 1);
261
+ };
206
262
  const options = useMemo(()=>found.rows.map(cardOf), [
207
263
  found.rows
208
264
  ]);
@@ -210,6 +266,11 @@ const narrowed = (filter)=>[
210
266
  const outcome = await purchase.buy(row.listingId);
211
267
  if (outcome !== 'owned') return;
212
268
  setOwnedHere((current)=>new Set(current).add(row.listingId));
269
+ setUnpaidHere((current)=>{
270
+ const next = new Set(current);
271
+ next.delete(row.listingId);
272
+ return next;
273
+ });
213
274
  onPick(_extends({}, row, {
214
275
  owned: true
215
276
  }));
@@ -308,6 +369,19 @@ const narrowed = (filter)=>[
308
369
  purchase.error ? /*#__PURE__*/ _jsx(Alert, {
309
370
  severity: "error",
310
371
  children: purchase.error
372
+ }) : null,
373
+ (readingPicked == null ? void 0 : readingPicked.failed) ? /*#__PURE__*/ _jsx(Alert, {
374
+ severity: "error",
375
+ "data-starter-answers-failed": pickedRow == null ? void 0 : pickedRow.listingId,
376
+ action: /*#__PURE__*/ _jsx(Button, {
377
+ color: "inherit",
378
+ size: "small",
379
+ onClick: readAgain,
380
+ children: 'Try again'
381
+ }),
382
+ children: readingPicked.failed
383
+ }) : readingPicked ? /*#__PURE__*/ _jsx(LinearProgress, {
384
+ "aria-label": "Reading this starter’s answers"
311
385
  }) : null
312
386
  ]
313
387
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/starter-picker-shelf.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport type { SiteStarterFacets } from '@aglyn/aglyn/app-utils/site-starter'\nimport type { ConsoleHostStarterPickerZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { SiteStarterSummary } from '@aglyn/aglyn/plugin-manager/plugin-site-starters'\nimport { mdiWeb } from '@aglyn/shared-data-mdi'\nimport {\n OptionCardGrid,\n type OptionCard,\n type OptionCardChip,\n type OptionCardImage,\n} from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport {\n Alert,\n Box,\n Button,\n Link,\n LinearProgress,\n Skeleton,\n Stack,\n type SxProps,\n type Theme,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { useStarterPurchase } from '../hooks/use-starter-purchase'\nimport { useStarterSearch } from '../hooks/use-starter-search'\nimport { starterCardFacts, starterPriceLabel, type StarterCardFact } from '../model/starter-query'\n\n/**\n * What a picked starter's price or licence says, held at the foot of the\n * visible cards while the shelf is in view, so \"Buy and continue\" is beside\n * whichever card was clicked rather than below all of them.\n */\nconst PICK_NOTE_SX: SxProps<Theme> = {\n position: 'sticky',\n bottom: (theme) => theme.spacing(2),\n zIndex: 1,\n boxShadow: 3,\n}\n\n/** The guide to site starters, linked from what the shelf says when it has nothing to show. */\nconst STARTERS_HELP = pluginDocsHelp('siteStarters')\n\n/**\n * A starter's preview as the card draws it: the size the platform's own\n * style previews are rendered at (square), so a row of cards stays level and\n * a publisher's picture is cropped to the same shape from its top.\n */\nconst PREVIEW_SIZE = { width: 800, height: 800 } as const\n\n/** How many placeholder cards stand in for the first answer. */\nconst PLACEHOLDER_CARDS = 3\n\n/** Statuses that mean this workspace may not have starters offered here: the dialog's own choice instead. */\nconst REFUSED = new Set([401, 403, 503])\n\n/** How each fact's chip is colored, from the theme: the price as the marketplace's own cards color it. */\nfunction chipOf(fact: StarterCardFact, row: SiteStarterSummary): OptionCardChip {\n switch (fact.key) {\n case 'price':\n return row.priceUsd > 0\n ? { label: fact.label, color: 'primary', variant: 'filled' }\n : { label: fact.label, color: 'success', variant: 'outlined' }\n case 'official':\n return { label: fact.label, color: 'primary', variant: 'outlined' }\n case 'ai':\n return { label: fact.label, color: 'secondary', variant: 'outlined' }\n default:\n return { label: fact.label }\n }\n}\n\n/** The picture a card draws, resolved the way the listing's own pages resolve it. */\nfunction previewOf(row: SiteStarterSummary): OptionCardImage | null {\n const src = resolveMediaSrc(row.previewImageUrl)\n if (!src) return null\n return { src, alt: `A preview of ${row.displayName || 'this starter'}`, ...PREVIEW_SIZE }\n}\n\n/** One starter as a card in the shared option grid. */\nfunction cardOf(row: SiteStarterSummary): OptionCard {\n const image = previewOf(row)\n return {\n id: row.listingId,\n title: row.displayName || 'Untitled starter',\n ...(row.authorName ? { description: `By ${row.authorName}` } : {}),\n ...(image ? { image } : { icon: mdiWeb.path }),\n chips: starterCardFacts(row).map((fact) => chipOf(fact, row)),\n }\n}\n\nconst narrowed = (filter: Partial<SiteStarterFacets>): boolean =>\n (['kind', 'base', 'fonts'] as const).some((key) => typeof filter[key] === 'string' && filter[key] !== '')\n\n/** A row with what the dialog or a purchase knows about owning it laid over the search's answer. */\nconst ownedOf = (row: SiteStarterSummary, override: boolean | undefined): boolean =>\n override ?? row.owned === true\n\n/** What an answer with no starters says, in the words of what was asked. */\nfunction emptyMessage(props: ConsoleHostStarterPickerZoneProps): string {\n if (props.search.trim()) return 'No starters match — try fewer words'\n if (narrowed(props.filter ?? {})) return 'No starters match these choices — try another'\n return props.mode === 'ai' ? 'No AI starters yet' : 'No starters yet'\n}\n\n/**\n * The marketplace's starters in \"Choose your starter\" (AGL-4049): the\n * `hostStarterPicker` zone, drawn before the site exists.\n *\n * Lists what the dialog asks for — its mode, its search words and its\n * filters — as cards of the shared option grid: a picture, the name, who made\n * it, and its price, kind, page count and the \"AI\" and platform marks as\n * chips. NOTHING is selected when it opens; a person picks, and the pick goes\n * to the dialog through `onPick`. It creates nothing: the dialog creates the\n * site from the starter it was handed.\n *\n * `ready` tells the dialog whether a picker is on screen. The first answer\n * decides: starters, or a search that found none (its empty state is a\n * picker with nothing in it yet), answer `true`; a workspace refused the\n * starters (no permission, the release flag off, no source) or a catalog with\n * nothing in it answers `false`, and the dialog offers its own choice\n * instead. Once shown, a later refusal hands back the same way; a later\n * failure is said here with a way to ask again.\n *\n * A picked starter with a price the workspace has not paid says so under\n * the cards, with \"Buy and continue\": the marketplace's payment, which brings\n * the person back to Create site on the same pick. One it owns says that\n * instead. The dialog's own `selected` wins over the shelf's pick: it is how\n * a pick restored after paying is shown chosen, and how one the create\n * refused is cleared.\n */\nexport function StarterPickerShelf(props: ConsoleHostStarterPickerZoneProps) {\n const { orgId, mode, search, filter, onPick, ready, selected } = props\n // The pick the dialog opened on, restored after paying for it: asked for\n // by every search from then on, so it is listed (first) wherever the\n // search would have put it, and never moves under the person's cursor.\n const [restored] = useState(() => selected?.listingId ?? null)\n const found = useStarterSearch({ orgId, mode, word: search, filter, include: restored })\n const [picked, setPicked] = useState<string | null>(null)\n const [shown, setShown] = useState(false)\n // Starters a purchase attempt found this workspace already owns.\n const [ownedHere, setOwnedHere] = useState<ReadonlySet<string>>(() => new Set())\n const purchase = useStarterPurchase(orgId)\n const value = selected !== undefined ? (selected?.listingId ?? null) : picked\n const pickedRow = value ? (found.rows.find((row) => row.listingId === value) ?? null) : null\n const ownedOverride = (row: SiteStarterSummary): boolean | undefined =>\n ownedHere.has(row.listingId) ? true : selected?.listingId === row.listingId ? selected.owned : undefined\n\n const refused = found.error !== null && REFUSED.has(found.error.status)\n // `null` until the first answer, which is the only one that may decide\n // there is nothing to show; after that only a refusal takes it back.\n const verdict: boolean | null = !found.settled\n ? null\n : refused\n ? false\n : shown\n ? true\n : found.error\n ? false\n : found.rows.length > 0 || search.trim() !== '' || narrowed(filter ?? {})\n\n useEffect(() => {\n if (verdict === true) setShown(true)\n if (verdict === false) setShown(false)\n }, [verdict])\n\n // Each distinct verdict once, whatever identity the dialog's callback has.\n const readyRef = useRef(ready)\n readyRef.current = ready\n const reported = useRef<boolean | null>(null)\n useEffect(() => {\n if (verdict === null || reported.current === verdict) return\n reported.current = verdict\n readyRef.current(verdict)\n }, [verdict])\n\n // A pick the dialog restored is handed back once the shelf has read it,\n // so the dialog holds the starter as the search describes it.\n const onPickRef = useRef(onPick)\n onPickRef.current = onPick\n const handedBack = useRef(false)\n useEffect(() => {\n if (!restored || handedBack.current || !pickedRow || pickedRow.listingId !== restored) return\n handedBack.current = true\n const override = ownedOverride(pickedRow)\n onPickRef.current(override === undefined ? pickedRow : { ...pickedRow, owned: override })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [restored, pickedRow])\n\n const options = useMemo(() => found.rows.map(cardOf), [found.rows])\n\n const buy = async (row: SiteStarterSummary) => {\n const outcome = await purchase.buy(row.listingId)\n if (outcome !== 'owned') return\n setOwnedHere((current) => new Set(current).add(row.listingId))\n onPick({ ...row, owned: true })\n }\n\n if (verdict === false) return null\n if (verdict === null) {\n return (\n <Box\n aria-busy\n aria-label=\"Loading starters\"\n sx={{ display: 'grid', gap: 2, gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, minmax(0, 1fr))', md: 'repeat(3, minmax(0, 1fr))' } }}\n >\n {Array.from({ length: PLACEHOLDER_CARDS }, (_, index) => (\n <Skeleton key={index} variant=\"rounded\" sx={{ height: 'auto', aspectRatio: '1 / 1', borderRadius: 2 }} />\n ))}\n </Box>\n )\n }\n\n return (\n <Stack spacing={2} data-starter-shelf={mode}>\n {found.loading ? <LinearProgress aria-label=\"Searching starters\" /> : null}\n {found.error ? (\n <Alert\n severity=\"error\"\n action={\n <Button color=\"inherit\" size=\"small\" onClick={found.refresh}>\n {'Try again'}\n </Button>\n }\n >\n {found.error.message}\n </Alert>\n ) : options.length === 0 ? (\n <Alert\n severity=\"info\"\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n component={Link}\n href={STARTERS_HELP.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {'How starters work'}\n </Button>\n }\n >\n {emptyMessage(props)}\n </Alert>\n ) : (\n <OptionCardGrid\n label={mode === 'ai' ? 'AI starters' : 'Starters'}\n options={options}\n value={value}\n columns={{ xs: 1, sm: 2, md: 3 }}\n onChange={(listingId) => {\n const row = found.rows.find((candidate) => candidate.listingId === listingId)\n if (!row) return\n setPicked(listingId)\n const override = ownedOverride(row)\n onPick(override === undefined ? row : { ...row, owned: override })\n }}\n />\n )}\n {pickedRow && pickedRow.priceUsd > 0 ? (\n ownedOf(pickedRow, ownedOverride(pickedRow)) ? (\n <Alert severity=\"success\" data-starter-owned={pickedRow.listingId} sx={PICK_NOTE_SX}>\n {'You own this starter. Create your site from it whenever you are ready.'}\n </Alert>\n ) : (\n <Alert\n severity=\"info\"\n data-starter-buy={pickedRow.listingId}\n sx={PICK_NOTE_SX}\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n disabled={purchase.busy || !orgId}\n onClick={() => void buy(pickedRow)}\n >\n {purchase.busy ? 'Opening…' : 'Buy and continue'}\n </Button>\n }\n >\n {`${pickedRow.displayName || 'This starter'} costs ${starterPriceLabel(pickedRow.priceUsd)}. ` +\n 'Buy it first; once paid, you come back here to create your site from it.'}\n </Alert>\n )\n ) : null}\n {purchase.error ? <Alert severity=\"error\">{purchase.error}</Alert> : null}\n </Stack>\n )\n}\n\nexport default StarterPickerShelf\n"],"names":["pluginDocsHelp","resolveMediaSrc","mdiWeb","OptionCardGrid","Alert","Box","Button","Link","LinearProgress","Skeleton","Stack","useEffect","useMemo","useRef","useState","useStarterPurchase","useStarterSearch","starterCardFacts","starterPriceLabel","PICK_NOTE_SX","position","bottom","theme","spacing","zIndex","boxShadow","STARTERS_HELP","PREVIEW_SIZE","width","height","PLACEHOLDER_CARDS","REFUSED","Set","chipOf","fact","row","key","priceUsd","label","color","variant","previewOf","src","previewImageUrl","alt","displayName","cardOf","image","id","listingId","title","authorName","description","icon","path","chips","map","narrowed","filter","some","ownedOf","override","owned","emptyMessage","props","search","trim","mode","StarterPickerShelf","found","orgId","onPick","ready","selected","restored","word","include","picked","setPicked","shown","setShown","ownedHere","setOwnedHere","purchase","value","undefined","pickedRow","rows","find","ownedOverride","has","refused","error","status","verdict","settled","length","readyRef","current","reported","onPickRef","handedBack","options","buy","outcome","add","aria-busy","aria-label","sx","display","gap","gridTemplateColumns","xs","sm","md","Array","from","_","index","aspectRatio","borderRadius","data-starter-shelf","loading","severity","action","size","onClick","refresh","message","component","href","target","rel","columns","onChange","candidate","data-starter-owned","data-starter-buy","disabled","busy"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,cAAc,QAAQ,mCAAkC;AACjE,SAASC,eAAe,QAAQ,mCAAkC;AAIlE,SAASC,MAAM,QAAQ,yBAAwB;AAC/C,SACEC,cAAc,QAIT,6DAA4D;AACnE,SACEC,KAAK,EACLC,GAAG,EACHC,MAAM,EACNC,IAAI,EACJC,cAAc,EACdC,QAAQ,EACRC,KAAK,QAGA,gBAAe;AACtB,SAASC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAC5D,SAASC,kBAAkB,QAAQ,mCAA+B;AAClE,SAASC,gBAAgB,QAAQ,iCAA6B;AAC9D,SAASC,gBAAgB,EAAEC,iBAAiB,QAA8B,4BAAwB;AAElG;;;;CAIC,GACD,MAAMC,eAA+B;IACnCC,UAAU;IACVC,QAAQ,CAACC,QAAUA,MAAMC,OAAO,CAAC;IACjCC,QAAQ;IACRC,WAAW;AACb;AAEA,6FAA6F,GAC7F,MAAMC,gBAAgB1B,eAAe;AAErC;;;;CAIC,GACD,MAAM2B,eAAe;IAAEC,OAAO;IAAKC,QAAQ;AAAI;AAE/C,8DAA8D,GAC9D,MAAMC,oBAAoB;AAE1B,2GAA2G,GAC3G,MAAMC,UAAU,IAAIC,IAAI;IAAC;IAAK;IAAK;CAAI;AAEvC,wGAAwG,GACxG,SAASC,OAAOC,IAAqB,EAAEC,GAAuB;IAC5D,OAAQD,KAAKE,GAAG;QACd,KAAK;YACH,OAAOD,IAAIE,QAAQ,GAAG,IAClB;gBAAEC,OAAOJ,KAAKI,KAAK;gBAAEC,OAAO;gBAAWC,SAAS;YAAS,IACzD;gBAAEF,OAAOJ,KAAKI,KAAK;gBAAEC,OAAO;gBAAWC,SAAS;YAAW;QACjE,KAAK;YACH,OAAO;gBAAEF,OAAOJ,KAAKI,KAAK;gBAAEC,OAAO;gBAAWC,SAAS;YAAW;QACpE,KAAK;YACH,OAAO;gBAAEF,OAAOJ,KAAKI,KAAK;gBAAEC,OAAO;gBAAaC,SAAS;YAAW;QACtE;YACE,OAAO;gBAAEF,OAAOJ,KAAKI,KAAK;YAAC;IAC/B;AACF;AAEA,mFAAmF,GACnF,SAASG,UAAUN,GAAuB;IACxC,MAAMO,MAAMzC,gBAAgBkC,IAAIQ,eAAe;IAC/C,IAAI,CAACD,KAAK,OAAO;IACjB,OAAO;QAAEA;QAAKE,KAAK,CAAC,aAAa,EAAET,IAAIU,WAAW,IAAI,gBAAgB;OAAKlB;AAC7E;AAEA,qDAAqD,GACrD,SAASmB,OAAOX,GAAuB;IACrC,MAAMY,QAAQN,UAAUN;IACxB,OAAO;QACLa,IAAIb,IAAIc,SAAS;QACjBC,OAAOf,IAAIU,WAAW,IAAI;OACtBV,IAAIgB,UAAU,GAAG;QAAEC,aAAa,CAAC,GAAG,EAAEjB,IAAIgB,UAAU,EAAE;IAAC,IAAI,CAAC,GAC5DJ,QAAQ;QAAEA;IAAM,IAAI;QAAEM,MAAMnD,OAAOoD,IAAI;IAAC;QAC5CC,OAAOtC,iBAAiBkB,KAAKqB,GAAG,CAAC,CAACtB,OAASD,OAAOC,MAAMC;;AAE5D;AAEA,MAAMsB,WAAW,CAACC,SAChB,AAAC;QAAC;QAAQ;QAAQ;KAAQ,CAAWC,IAAI,CAAC,CAACvB,MAAQ,OAAOsB,MAAM,CAACtB,IAAI,KAAK,YAAYsB,MAAM,CAACtB,IAAI,KAAK;AAExG,kGAAkG,GAClG,MAAMwB,UAAU,CAACzB,KAAyB0B,WACxCA,mBAAAA,WAAY1B,IAAI2B,KAAK,KAAK;AAE5B,0EAA0E,GAC1E,SAASC,aAAaC,KAAwC;QAE/CA;IADb,IAAIA,MAAMC,MAAM,CAACC,IAAI,IAAI,OAAO;IAChC,IAAIT,UAASO,gBAAAA,MAAMN,MAAM,YAAZM,gBAAgB,CAAC,IAAI,OAAO;IACzC,OAAOA,MAAMG,IAAI,KAAK,OAAO,uBAAuB;AACtD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASC,mBAAmBJ,KAAwC;cAa9CK;IAZ3B,MAAM,EAAEC,KAAK,EAAEH,IAAI,EAAEF,MAAM,EAAEP,MAAM,EAAEa,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAGT;IACjE,yEAAyE;IACzE,qEAAqE;IACrE,uEAAuE;IACvE,MAAM,CAACU,SAAS,GAAG5D,SAAS;;uBAAM2D,4BAAAA,SAAUxB,SAAS,mBAAI;;IACzD,MAAMoB,QAAQrD,iBAAiB;QAAEsD;QAAOH;QAAMQ,MAAMV;QAAQP;QAAQkB,SAASF;IAAS;IACtF,MAAM,CAACG,QAAQC,UAAU,GAAGhE,SAAwB;IACpD,MAAM,CAACiE,OAAOC,SAAS,GAAGlE,SAAS;IACnC,iEAAiE;IACjE,MAAM,CAACmE,WAAWC,aAAa,GAAGpE,SAA8B,IAAM,IAAIkB;IAC1E,MAAMmD,WAAWpE,mBAAmBuD;IACpC,MAAMc,QAAQX,aAAaY,oBAAaZ,4BAAAA,SAAUxB,SAAS,mBAAI,OAAQ4B;IACvE,MAAMS,YAAYF,SAASf,mBAAAA,MAAMkB,IAAI,CAACC,IAAI,CAAC,CAACrD,MAAQA,IAAIc,SAAS,KAAKmC,kBAA3Cf,mBAAqD,OAAQ;IACxF,MAAMoB,gBAAgB,CAACtD,MACrB8C,UAAUS,GAAG,CAACvD,IAAIc,SAAS,IAAI,OAAOwB,CAAAA,4BAAAA,SAAUxB,SAAS,MAAKd,IAAIc,SAAS,GAAGwB,SAASX,KAAK,GAAGuB;IAEjG,MAAMM,UAAUtB,MAAMuB,KAAK,KAAK,QAAQ7D,QAAQ2D,GAAG,CAACrB,MAAMuB,KAAK,CAACC,MAAM;IACtE,uEAAuE;IACvE,qEAAqE;IACrE,MAAMC,UAA0B,CAACzB,MAAM0B,OAAO,GAC1C,OACAJ,UACE,QACAZ,QACE,OACAV,MAAMuB,KAAK,GACT,QACAvB,MAAMkB,IAAI,CAACS,MAAM,GAAG,KAAK/B,OAAOC,IAAI,OAAO,MAAMT,SAASC,iBAAAA,SAAU,CAAC;IAE/E/C,UAAU;QACR,IAAImF,YAAY,MAAMd,SAAS;QAC/B,IAAIc,YAAY,OAAOd,SAAS;IAClC,GAAG;QAACc;KAAQ;IAEZ,2EAA2E;IAC3E,MAAMG,WAAWpF,OAAO2D;IACxByB,SAASC,OAAO,GAAG1B;IACnB,MAAM2B,WAAWtF,OAAuB;IACxCF,UAAU;QACR,IAAImF,YAAY,QAAQK,SAASD,OAAO,KAAKJ,SAAS;QACtDK,SAASD,OAAO,GAAGJ;QACnBG,SAASC,OAAO,CAACJ;IACnB,GAAG;QAACA;KAAQ;IAEZ,wEAAwE;IACxE,8DAA8D;IAC9D,MAAMM,YAAYvF,OAAO0D;IACzB6B,UAAUF,OAAO,GAAG3B;IACpB,MAAM8B,aAAaxF,OAAO;IAC1BF,UAAU;QACR,IAAI,CAAC+D,YAAY2B,WAAWH,OAAO,IAAI,CAACZ,aAAaA,UAAUrC,SAAS,KAAKyB,UAAU;QACvF2B,WAAWH,OAAO,GAAG;QACrB,MAAMrC,WAAW4B,cAAcH;QAC/Bc,UAAUF,OAAO,CAACrC,aAAawB,YAAYC,YAAY,aAAKA;YAAWxB,OAAOD;;IAC9E,uDAAuD;IACzD,GAAG;QAACa;QAAUY;KAAU;IAExB,MAAMgB,UAAU1F,QAAQ,IAAMyD,MAAMkB,IAAI,CAAC/B,GAAG,CAACV,SAAS;QAACuB,MAAMkB,IAAI;KAAC;IAElE,MAAMgB,MAAM,OAAOpE;QACjB,MAAMqE,UAAU,MAAMrB,SAASoB,GAAG,CAACpE,IAAIc,SAAS;QAChD,IAAIuD,YAAY,SAAS;QACzBtB,aAAa,CAACgB,UAAY,IAAIlE,IAAIkE,SAASO,GAAG,CAACtE,IAAIc,SAAS;QAC5DsB,OAAO,aAAKpC;YAAK2B,OAAO;;IAC1B;IAEA,IAAIgC,YAAY,OAAO,OAAO;IAC9B,IAAIA,YAAY,MAAM;QACpB,qBACE,KAACzF;YACCqG,WAAS;YACTC,cAAW;YACXC,IAAI;gBAAEC,SAAS;gBAAQC,KAAK;gBAAGC,qBAAqB;oBAAEC,IAAI;oBAAOC,IAAI;oBAA6BC,IAAI;gBAA4B;YAAE;sBAEnIC,MAAMC,IAAI,CAAC;gBAAEpB,QAAQlE;YAAkB,GAAG,CAACuF,GAAGC,sBAC7C,KAAC7G;oBAAqB+B,SAAQ;oBAAUoE,IAAI;wBAAE/E,QAAQ;wBAAQ0F,aAAa;wBAASC,cAAc;oBAAE;mBAArFF;;IAIvB;IAEA,qBACE,MAAC5G;QAAMa,SAAS;QAAGkG,sBAAoBtD;;YACpCE,MAAMqD,OAAO,iBAAG,KAAClH;gBAAemG,cAAW;iBAA0B;YACrEtC,MAAMuB,KAAK,iBACV,KAACxF;gBACCuH,UAAS;gBACTC,sBACE,KAACtH;oBAAOiC,OAAM;oBAAUsF,MAAK;oBAAQC,SAASzD,MAAM0D,OAAO;8BACxD;;0BAIJ1D,MAAMuB,KAAK,CAACoC,OAAO;iBAEpB1B,QAAQN,MAAM,KAAK,kBACrB,KAAC5F;gBACCuH,UAAS;gBACTC,sBACE,KAACtH;oBACCiC,OAAM;oBACNsF,MAAK;oBACLI,WAAW1H;oBACX2H,MAAMxG,cAAcwG,IAAI;oBACxBC,QAAO;oBACPC,KAAI;8BAEH;;0BAIJrE,aAAaC;+BAGhB,KAAC7D;gBACCmC,OAAO6B,SAAS,OAAO,gBAAgB;gBACvCmC,SAASA;gBACTlB,OAAOA;gBACPiD,SAAS;oBAAErB,IAAI;oBAAGC,IAAI;oBAAGC,IAAI;gBAAE;gBAC/BoB,UAAU,CAACrF;oBACT,MAAMd,MAAMkC,MAAMkB,IAAI,CAACC,IAAI,CAAC,CAAC+C,YAAcA,UAAUtF,SAAS,KAAKA;oBACnE,IAAI,CAACd,KAAK;oBACV2C,UAAU7B;oBACV,MAAMY,WAAW4B,cAActD;oBAC/BoC,OAAOV,aAAawB,YAAYlD,MAAM,aAAKA;wBAAK2B,OAAOD;;gBACzD;;YAGHyB,aAAaA,UAAUjD,QAAQ,GAAG,IACjCuB,QAAQ0B,WAAWG,cAAcH,4BAC/B,KAAClF;gBAAMuH,UAAS;gBAAUa,sBAAoBlD,UAAUrC,SAAS;gBAAE2D,IAAIzF;0BACpE;+BAGH,KAACf;gBACCuH,UAAS;gBACTc,oBAAkBnD,UAAUrC,SAAS;gBACrC2D,IAAIzF;gBACJyG,sBACE,KAACtH;oBACCiC,OAAM;oBACNsF,MAAK;oBACLa,UAAUvD,SAASwD,IAAI,IAAI,CAACrE;oBAC5BwD,SAAS,IAAM,KAAKvB,IAAIjB;8BAEvBH,SAASwD,IAAI,GAAG,aAAa;;0BAIjC,GAAGrD,UAAUzC,WAAW,IAAI,eAAe,OAAO,EAAE3B,kBAAkBoE,UAAUjD,QAAQ,EAAE,EAAE,CAAC,GAC5F;iBAGJ;YACH8C,SAASS,KAAK,iBAAG,KAACxF;gBAAMuH,UAAS;0BAASxC,SAASS,KAAK;iBAAY;;;AAG3E;AAEA,eAAexB,mBAAkB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/starter-picker-shelf.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport type { SiteStarterFacets } from '@aglyn/aglyn/app-utils/site-starter'\nimport type { ConsoleHostStarterPickerZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { SiteStarterSummary } from '@aglyn/aglyn/plugin-manager/plugin-site-starters'\nimport { mdiWeb } from '@aglyn/shared-data-mdi'\nimport {\n OptionCardGrid,\n type OptionCard,\n type OptionCardChip,\n type OptionCardImage,\n} from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport {\n Alert,\n Box,\n Button,\n Link,\n LinearProgress,\n Skeleton,\n Stack,\n type SxProps,\n type Theme,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { useStarterAnswers } from '../hooks/use-starter-answers'\nimport { useStarterPurchase } from '../hooks/use-starter-purchase'\nimport { useStarterSearch } from '../hooks/use-starter-search'\nimport { starterCardFacts, starterPriceLabel, type StarterCardFact } from '../model/starter-query'\n\n/**\n * What a picked starter's price or licence says, held at the foot of the\n * visible cards while the shelf is in view, so \"Buy and continue\" is beside\n * whichever card was clicked rather than below all of them.\n */\nconst PICK_NOTE_SX: SxProps<Theme> = {\n position: 'sticky',\n bottom: (theme) => theme.spacing(2),\n zIndex: 1,\n boxShadow: 3,\n}\n\n/** The guide to site starters, linked from what the shelf says when it has nothing to show. */\nconst STARTERS_HELP = pluginDocsHelp('siteStarters')\n\n/**\n * A starter's preview as the card draws it: the size the platform's own\n * style previews are rendered at (square), so a row of cards stays level and\n * a publisher's picture is cropped to the same shape from its top.\n */\nconst PREVIEW_SIZE = { width: 800, height: 800 } as const\n\n/** How many placeholder cards stand in for the first answer. */\nconst PLACEHOLDER_CARDS = 3\n\n/** Statuses that mean this workspace may not have starters offered here: the dialog's own choice instead. */\nconst REFUSED = new Set([401, 403, 503])\n\n/** How each fact's chip is colored, from the theme: the price as the marketplace's own cards color it. */\nfunction chipOf(fact: StarterCardFact, row: SiteStarterSummary): OptionCardChip {\n switch (fact.key) {\n case 'price':\n return row.priceUsd > 0\n ? { label: fact.label, color: 'primary', variant: 'filled' }\n : { label: fact.label, color: 'success', variant: 'outlined' }\n case 'official':\n return { label: fact.label, color: 'primary', variant: 'outlined' }\n case 'ai':\n return { label: fact.label, color: 'secondary', variant: 'outlined' }\n default:\n return { label: fact.label }\n }\n}\n\n/** The picture a card draws, resolved the way the listing's own pages resolve it. */\nfunction previewOf(row: SiteStarterSummary): OptionCardImage | null {\n const src = resolveMediaSrc(row.previewImageUrl)\n if (!src) return null\n return { src, alt: `A preview of ${row.displayName || 'this starter'}`, ...PREVIEW_SIZE }\n}\n\n/** One starter as a card in the shared option grid. */\nfunction cardOf(row: SiteStarterSummary): OptionCard {\n const image = previewOf(row)\n return {\n id: row.listingId,\n title: row.displayName || 'Untitled starter',\n ...(row.authorName ? { description: `By ${row.authorName}` } : {}),\n ...(image ? { image } : { icon: mdiWeb.path }),\n chips: starterCardFacts(row).map((fact) => chipOf(fact, row)),\n }\n}\n\nconst narrowed = (filter: Partial<SiteStarterFacets>): boolean =>\n (['kind', 'base', 'fonts'] as const).some((key) => typeof filter[key] === 'string' && filter[key] !== '')\n\n/** A row with what the dialog or a purchase knows about owning it laid over the search's answer. */\nconst ownedOf = (row: SiteStarterSummary, override: boolean | undefined): boolean =>\n override ?? row.owned === true\n\n/** What an answer with no starters says, in the words of what was asked. */\nfunction emptyMessage(props: ConsoleHostStarterPickerZoneProps): string {\n if (props.search.trim()) return 'No starters match — try fewer words'\n if (narrowed(props.filter ?? {})) return 'No starters match these choices — try another'\n return props.mode === 'ai' ? 'No AI starters yet' : 'No starters yet'\n}\n\n/**\n * The marketplace's starters in \"Choose your starter\" (AGL-4049): the\n * `hostStarterPicker` zone, drawn before the site exists.\n *\n * Lists what the dialog asks for — its mode, its search words and its\n * filters — as cards of the shared option grid: a picture, the name, who made\n * it, and its price, kind, page count and the \"AI\" and platform marks as\n * chips. NOTHING is selected when it opens; a person picks, and the pick goes\n * to the dialog through `onPick`. It creates nothing: the dialog creates the\n * site from the starter it was handed.\n *\n * `ready` tells the dialog whether a picker is on screen. The first answer\n * decides: starters, or a search that found none (its empty state is a\n * picker with nothing in it yet), answer `true`; a workspace refused the\n * starters (no permission, the release flag off, no source) or a catalog with\n * nothing in it answers `false`, and the dialog offers its own choice\n * instead. Once shown, a later refusal hands back the same way; a later\n * failure is said here with a way to ask again.\n *\n * A picked starter with a price the workspace has not paid says so under\n * the cards, with \"Buy and continue\": the marketplace's payment, which brings\n * the person back to Create site on the same pick. One it owns says that\n * instead. The dialog's own `selected` wins over the shelf's pick: it is how\n * a pick restored after paying is shown chosen, and how one the create\n * refused is cleared.\n *\n * An AI starter the workspace may use — free, owned, or just bought — has\n * the answers it carries read once it is picked (AGL-4049 phase 4), and\n * handed to the dialog with it (`onPick(pick, { answers })`), which goes on\n * to the AI start's questions with them. A priced one not yet bought is\n * never read: its answers are what it sells, and the door would ask payment\n * for them. A door that still asks is told back to the dialog as unowned.\n */\nexport function StarterPickerShelf(props: ConsoleHostStarterPickerZoneProps) {\n const { orgId, mode, search, filter, onPick, ready, selected } = props\n // The pick the dialog opened on, restored after paying for it: asked for\n // by every search from then on, so it is listed (first) wherever the\n // search would have put it, and never moves under the person's cursor.\n const [restored] = useState(() => selected?.listingId ?? null)\n const found = useStarterSearch({ orgId, mode, word: search, filter, include: restored })\n const [picked, setPicked] = useState<string | null>(null)\n const [shown, setShown] = useState(false)\n // Starters a purchase attempt found this workspace already owns.\n const [ownedHere, setOwnedHere] = useState<ReadonlySet<string>>(() => new Set())\n // AI starters whose answers the door still asked payment for (AGL-4049).\n const [unpaidHere, setUnpaidHere] = useState<ReadonlySet<string>>(() => new Set())\n const purchase = useStarterPurchase(orgId)\n const starterAnswers = useStarterAnswers(orgId)\n const value = selected !== undefined ? (selected?.listingId ?? null) : picked\n const pickedRow = value ? (found.rows.find((row) => row.listingId === value) ?? null) : null\n const ownedOverride = (row: SiteStarterSummary): boolean | undefined =>\n ownedHere.has(row.listingId)\n ? true\n : unpaidHere.has(row.listingId)\n ? false\n : selected?.listingId === row.listingId\n ? selected.owned\n : undefined\n\n const refused = found.error !== null && REFUSED.has(found.error.status)\n // `null` until the first answer, which is the only one that may decide\n // there is nothing to show; after that only a refusal takes it back.\n const verdict: boolean | null = !found.settled\n ? null\n : refused\n ? false\n : shown\n ? true\n : found.error\n ? false\n : found.rows.length > 0 || search.trim() !== '' || narrowed(filter ?? {})\n\n useEffect(() => {\n if (verdict === true) setShown(true)\n if (verdict === false) setShown(false)\n }, [verdict])\n\n // Each distinct verdict once, whatever identity the dialog's callback has.\n const readyRef = useRef(ready)\n readyRef.current = ready\n const reported = useRef<boolean | null>(null)\n useEffect(() => {\n if (verdict === null || reported.current === verdict) return\n reported.current = verdict\n readyRef.current(verdict)\n }, [verdict])\n\n // A pick the dialog restored is handed back once the shelf has read it,\n // so the dialog holds the starter as the search describes it.\n const onPickRef = useRef(onPick)\n onPickRef.current = onPick\n const handedBack = useRef(false)\n useEffect(() => {\n if (!restored || handedBack.current || !pickedRow || pickedRow.listingId !== restored) return\n handedBack.current = true\n const override = ownedOverride(pickedRow)\n onPickRef.current(override === undefined ? pickedRow : { ...pickedRow, owned: override })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [restored, pickedRow])\n\n // The picked AI starter's answers (AGL-4049), read once it is one the\n // workspace may use; keyed on the listing and whether it is owned, so a\n // purchase that makes it usable reads it, and nothing reads it twice.\n const pickedOwned = pickedRow ? ownedOf(pickedRow, ownedOverride(pickedRow)) : false\n const answersKey =\n pickedRow && pickedRow.facets.mode === 'ai' && (pickedRow.priceUsd <= 0 || pickedOwned)\n ? `${pickedRow.listingId}:${pickedOwned}`\n : null\n const [reading, setReading] = useState<{ key: string; failed: string | null } | null>(null)\n const [readAttempt, setReadAttempt] = useState(0)\n const askedAnswers = useRef<string | null>(null)\n useEffect(() => {\n if (!answersKey || !pickedRow || askedAnswers.current === answersKey) return\n askedAnswers.current = answersKey\n const row = pickedRow\n const override = ownedOverride(row)\n const handed = override === undefined ? row : { ...row, owned: override }\n setReading({ key: answersKey, failed: null })\n void starterAnswers.read(row.listingId).then((outcome) => {\n if (askedAnswers.current !== answersKey) return\n if (outcome.status === 'failed') return setReading({ key: answersKey, failed: outcome.message })\n setReading(null)\n if (outcome.status === 'ready') return onPickRef.current(handed, { answers: outcome.answers })\n setUnpaidHere((current) => new Set(current).add(row.listingId))\n onPickRef.current({ ...row, owned: false })\n })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [answersKey, readAttempt])\n const readingPicked = reading !== null && reading.key === answersKey ? reading : null\n const readAgain = () => {\n askedAnswers.current = null\n setReadAttempt((count) => count + 1)\n }\n\n const options = useMemo(() => found.rows.map(cardOf), [found.rows])\n\n const buy = async (row: SiteStarterSummary) => {\n const outcome = await purchase.buy(row.listingId)\n if (outcome !== 'owned') return\n setOwnedHere((current) => new Set(current).add(row.listingId))\n setUnpaidHere((current) => {\n const next = new Set(current)\n next.delete(row.listingId)\n return next\n })\n onPick({ ...row, owned: true })\n }\n\n if (verdict === false) return null\n if (verdict === null) {\n return (\n <Box\n aria-busy\n aria-label=\"Loading starters\"\n sx={{ display: 'grid', gap: 2, gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, minmax(0, 1fr))', md: 'repeat(3, minmax(0, 1fr))' } }}\n >\n {Array.from({ length: PLACEHOLDER_CARDS }, (_, index) => (\n <Skeleton key={index} variant=\"rounded\" sx={{ height: 'auto', aspectRatio: '1 / 1', borderRadius: 2 }} />\n ))}\n </Box>\n )\n }\n\n return (\n <Stack spacing={2} data-starter-shelf={mode}>\n {found.loading ? <LinearProgress aria-label=\"Searching starters\" /> : null}\n {found.error ? (\n <Alert\n severity=\"error\"\n action={\n <Button color=\"inherit\" size=\"small\" onClick={found.refresh}>\n {'Try again'}\n </Button>\n }\n >\n {found.error.message}\n </Alert>\n ) : options.length === 0 ? (\n <Alert\n severity=\"info\"\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n component={Link}\n href={STARTERS_HELP.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {'How starters work'}\n </Button>\n }\n >\n {emptyMessage(props)}\n </Alert>\n ) : (\n <OptionCardGrid\n label={mode === 'ai' ? 'AI starters' : 'Starters'}\n options={options}\n value={value}\n columns={{ xs: 1, sm: 2, md: 3 }}\n onChange={(listingId) => {\n const row = found.rows.find((candidate) => candidate.listingId === listingId)\n if (!row) return\n setPicked(listingId)\n const override = ownedOverride(row)\n onPick(override === undefined ? row : { ...row, owned: override })\n }}\n />\n )}\n {pickedRow && pickedRow.priceUsd > 0 ? (\n ownedOf(pickedRow, ownedOverride(pickedRow)) ? (\n <Alert severity=\"success\" data-starter-owned={pickedRow.listingId} sx={PICK_NOTE_SX}>\n {'You own this starter. Create your site from it whenever you are ready.'}\n </Alert>\n ) : (\n <Alert\n severity=\"info\"\n data-starter-buy={pickedRow.listingId}\n sx={PICK_NOTE_SX}\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n disabled={purchase.busy || !orgId}\n onClick={() => void buy(pickedRow)}\n >\n {purchase.busy ? 'Opening…' : 'Buy and continue'}\n </Button>\n }\n >\n {`${pickedRow.displayName || 'This starter'} costs ${starterPriceLabel(pickedRow.priceUsd)}. ` +\n 'Buy it first; once paid, you come back here to create your site from it.'}\n </Alert>\n )\n ) : null}\n {purchase.error ? <Alert severity=\"error\">{purchase.error}</Alert> : null}\n {readingPicked?.failed ? (\n <Alert\n severity=\"error\"\n data-starter-answers-failed={pickedRow?.listingId}\n action={\n <Button color=\"inherit\" size=\"small\" onClick={readAgain}>\n {'Try again'}\n </Button>\n }\n >\n {readingPicked.failed}\n </Alert>\n ) : readingPicked ? (\n <LinearProgress aria-label=\"Reading this starter’s answers\" />\n ) : null}\n </Stack>\n )\n}\n\nexport default StarterPickerShelf\n"],"names":["pluginDocsHelp","resolveMediaSrc","mdiWeb","OptionCardGrid","Alert","Box","Button","Link","LinearProgress","Skeleton","Stack","useEffect","useMemo","useRef","useState","useStarterAnswers","useStarterPurchase","useStarterSearch","starterCardFacts","starterPriceLabel","PICK_NOTE_SX","position","bottom","theme","spacing","zIndex","boxShadow","STARTERS_HELP","PREVIEW_SIZE","width","height","PLACEHOLDER_CARDS","REFUSED","Set","chipOf","fact","row","key","priceUsd","label","color","variant","previewOf","src","previewImageUrl","alt","displayName","cardOf","image","id","listingId","title","authorName","description","icon","path","chips","map","narrowed","filter","some","ownedOf","override","owned","emptyMessage","props","search","trim","mode","StarterPickerShelf","found","orgId","onPick","ready","selected","restored","word","include","picked","setPicked","shown","setShown","ownedHere","setOwnedHere","unpaidHere","setUnpaidHere","purchase","starterAnswers","value","undefined","pickedRow","rows","find","ownedOverride","has","refused","error","status","verdict","settled","length","readyRef","current","reported","onPickRef","handedBack","pickedOwned","answersKey","facets","reading","setReading","readAttempt","setReadAttempt","askedAnswers","handed","failed","read","then","outcome","message","answers","add","readingPicked","readAgain","count","options","buy","next","delete","aria-busy","aria-label","sx","display","gap","gridTemplateColumns","xs","sm","md","Array","from","_","index","aspectRatio","borderRadius","data-starter-shelf","loading","severity","action","size","onClick","refresh","component","href","target","rel","columns","onChange","candidate","data-starter-owned","data-starter-buy","disabled","busy","data-starter-answers-failed"],"mappings":"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"}
@@ -0,0 +1,41 @@
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
+ /** How reading one AI starter's answers ended. */
18
+ export type StarterAnswersOutcome =
19
+ /** The answers it carries, to fill the AI start's questions. */
20
+ {
21
+ status: 'ready';
22
+ answers: Record<string, unknown>;
23
+ }
24
+ /** A priced starter the workspace has not bought: bought first, then read. */
25
+ | {
26
+ status: 'purchase';
27
+ }
28
+ /** They could not be read; the words to show. */
29
+ | {
30
+ status: 'failed';
31
+ message: string;
32
+ };
33
+ /**
34
+ * Reads the answers an AI starter carries (AGL-4049 phase 4), once a person
35
+ * picks it in "Choose your starter": `GET /api/marketplace/starters/answers`
36
+ * with the person's own token, for the workspace the site is for. A 402 is a
37
+ * starter to buy first, never a failure.
38
+ */
39
+ export declare function useStarterAnswers(orgId: string | undefined): {
40
+ read: (listingId: string) => Promise<StarterAnswersOutcome>;
41
+ };
@@ -0,0 +1,75 @@
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
+ */ 'use client';
17
+ import { lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn/app-utils/lockdown";
18
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
19
+ import { useUser } from "@aglyn/tenant-feature-instance";
20
+ import { useCallback, useRef } from "react";
21
+ import { readStarterAnswersAnswer, STARTER_ANSWERS_PATH, starterAnswersParams } from "../model/starter-query.js";
22
+ /**
23
+ * Reads the answers an AI starter carries (AGL-4049 phase 4), once a person
24
+ * picks it in "Choose your starter": `GET /api/marketplace/starters/answers`
25
+ * with the person's own token, for the workspace the site is for. A 402 is a
26
+ * starter to buy first, never a failure.
27
+ */ export function useStarterAnswers(orgId) {
28
+ const { data: user } = useUser();
29
+ const userRef = useRef(user);
30
+ userRef.current = user;
31
+ const read = useCallback(async (listingId)=>{
32
+ if (!orgId || !listingId) return {
33
+ status: 'failed',
34
+ message: 'This starter could not be read.'
35
+ };
36
+ try {
37
+ const response = await authorizedFetch(userRef.current, `${STARTER_ANSWERS_PATH}?${starterAnswersParams({
38
+ orgId,
39
+ listingId
40
+ }).toString()}`);
41
+ const payload = await response.json().catch(()=>null);
42
+ if (response.status === 402) return {
43
+ status: 'purchase'
44
+ };
45
+ const parsed = response.ok ? readStarterAnswersAnswer(payload) : null;
46
+ if (parsed) return {
47
+ status: 'ready',
48
+ answers: parsed.answers
49
+ };
50
+ // A lockdown is not a broken starter (AGL-1532): it says what is paused.
51
+ const locked = parseLockdownRefusal(response.status, payload);
52
+ if (locked) return {
53
+ status: 'failed',
54
+ message: lockdownRefusalText(locked)
55
+ };
56
+ const error = payload == null ? void 0 : payload.error;
57
+ return {
58
+ status: 'failed',
59
+ message: typeof error === 'string' && error ? error : 'This starter could not be read.'
60
+ };
61
+ } catch (unused) {
62
+ return {
63
+ status: 'failed',
64
+ message: 'This starter could not be read. Check your connection and try again.'
65
+ };
66
+ }
67
+ }, [
68
+ orgId
69
+ ]);
70
+ return {
71
+ read
72
+ };
73
+ }
74
+
75
+ //# sourceMappingURL=use-starter-answers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/hooks/use-starter-answers.ts"],"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\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn/app-utils/lockdown'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useRef } from 'react'\nimport { readStarterAnswersAnswer, STARTER_ANSWERS_PATH, starterAnswersParams } from '../model/starter-query'\n\n/** How reading one AI starter's answers ended. */\nexport type StarterAnswersOutcome =\n /** The answers it carries, to fill the AI start's questions. */\n | { status: 'ready'; answers: Record<string, unknown> }\n /** A priced starter the workspace has not bought: bought first, then read. */\n | { status: 'purchase' }\n /** They could not be read; the words to show. */\n | { status: 'failed'; message: string }\n\n/**\n * Reads the answers an AI starter carries (AGL-4049 phase 4), once a person\n * picks it in \"Choose your starter\": `GET /api/marketplace/starters/answers`\n * with the person's own token, for the workspace the site is for. A 402 is a\n * starter to buy first, never a failure.\n */\nexport function useStarterAnswers(orgId: string | undefined): {\n read: (listingId: string) => Promise<StarterAnswersOutcome>\n} {\n const { data: user } = useUser()\n const userRef = useRef(user)\n userRef.current = user\n const read = useCallback(\n async (listingId: string): Promise<StarterAnswersOutcome> => {\n if (!orgId || !listingId) return { status: 'failed', message: 'This starter could not be read.' }\n try {\n const response = await authorizedFetch(\n userRef.current,\n `${STARTER_ANSWERS_PATH}?${starterAnswersParams({ orgId, listingId }).toString()}`,\n )\n const payload: unknown = await response.json().catch((): null => null)\n if (response.status === 402) return { status: 'purchase' }\n const parsed = response.ok ? readStarterAnswersAnswer(payload) : null\n if (parsed) return { status: 'ready', answers: parsed.answers }\n // A lockdown is not a broken starter (AGL-1532): it says what is paused.\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) return { status: 'failed', message: lockdownRefusalText(locked) }\n const error = (payload as { error?: unknown } | null)?.error\n return {\n status: 'failed',\n message: typeof error === 'string' && error ? error : 'This starter could not be read.',\n }\n } catch {\n return { status: 'failed', message: 'This starter could not be read. Check your connection and try again.' }\n }\n },\n [orgId],\n )\n return { read }\n}\n"],"names":["lockdownRefusalText","parseLockdownRefusal","authorizedFetch","useUser","useCallback","useRef","readStarterAnswersAnswer","STARTER_ANSWERS_PATH","starterAnswersParams","useStarterAnswers","orgId","data","user","userRef","current","read","listingId","status","message","response","toString","payload","json","catch","parsed","ok","answers","locked","error"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;AAEA,SAASA,mBAAmB,EAAEC,oBAAoB,QAAQ,kCAAiC;AAC3F,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,WAAW,EAAEC,MAAM,QAAQ,QAAO;AAC3C,SAASC,wBAAwB,EAAEC,oBAAoB,EAAEC,oBAAoB,QAAQ,4BAAwB;AAW7G;;;;;CAKC,GACD,OAAO,SAASC,kBAAkBC,KAAyB;IAGzD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGT;IACvB,MAAMU,UAAUR,OAAOO;IACvBC,QAAQC,OAAO,GAAGF;IAClB,MAAMG,OAAOX,YACX,OAAOY;QACL,IAAI,CAACN,SAAS,CAACM,WAAW,OAAO;YAAEC,QAAQ;YAAUC,SAAS;QAAkC;QAChG,IAAI;YACF,MAAMC,WAAW,MAAMjB,gBACrBW,QAAQC,OAAO,EACf,GAAGP,qBAAqB,CAAC,EAAEC,qBAAqB;gBAAEE;gBAAOM;YAAU,GAAGI,QAAQ,IAAI;YAEpF,MAAMC,UAAmB,MAAMF,SAASG,IAAI,GAAGC,KAAK,CAAC,IAAY;YACjE,IAAIJ,SAASF,MAAM,KAAK,KAAK,OAAO;gBAAEA,QAAQ;YAAW;YACzD,MAAMO,SAASL,SAASM,EAAE,GAAGnB,yBAAyBe,WAAW;YACjE,IAAIG,QAAQ,OAAO;gBAAEP,QAAQ;gBAASS,SAASF,OAAOE,OAAO;YAAC;YAC9D,yEAAyE;YACzE,MAAMC,SAAS1B,qBAAqBkB,SAASF,MAAM,EAAEI;YACrD,IAAIM,QAAQ,OAAO;gBAAEV,QAAQ;gBAAUC,SAASlB,oBAAoB2B;YAAQ;YAC5E,MAAMC,QAASP,2BAAD,AAACA,QAAwCO,KAAK;YAC5D,OAAO;gBACLX,QAAQ;gBACRC,SAAS,OAAOU,UAAU,YAAYA,QAAQA,QAAQ;YACxD;QACF,EAAE,eAAM;YACN,OAAO;gBAAEX,QAAQ;gBAAUC,SAAS;YAAuE;QAC7G;IACF,GACA;QAACR;KAAM;IAET,OAAO;QAAEK;IAAK;AAChB"}
@@ -125,8 +125,21 @@ export declare function licenceBase(owner: {
125
125
  }, listingIds: readonly string[] | null): ListQueryFilter[];
126
126
  /** The queue's Status select, as clauses the query serves. */
127
127
  export declare const REVIEW_QUEUE_QUERY: ListQueryDeclaration;
128
- /** The three sections the queue reads, each its own query. */
129
- export type ReviewQueueSection = 'queue' | 'listed' | 'verification';
128
+ /** The sections the queue reads, each its own query. */
129
+ export type ReviewQueueSection = 'queue' | 'listed' | 'verification' | 'starters';
130
+ /**
131
+ * The Starters section's query (AGL-4049): the same Status select and search
132
+ * as the plugin sections, read newest first. The plugin sections read in id
133
+ * order, which equality alone serves; this one pairs each field it can ask
134
+ * with `createdAt` descending, the composites the index file carries for it.
135
+ */
136
+ export declare const STARTER_REVIEW_QUEUE_QUERY: ListQueryDeclaration;
137
+ /** What the Starters section shows while the Status select says All: the starters waiting for review. */
138
+ export declare const STARTER_AWAITING_REVIEW_STATUS = "submitted";
139
+ /** The predicates the Starters section can scope by, for its index enumeration. */
140
+ export declare const STARTER_REVIEW_QUEUE_BASE_PATHS: readonly {
141
+ path: string;
142
+ }[];
130
143
  /** A listing's newest bytes have no approval: the `latestVersionReviewState` values that mean it. */
131
144
  export declare const AWAITING_REVIEW_STATES: readonly ["pending", "rejected"];
132
145
  export declare const LISTED_REVIEW_STATUSES: readonly ["listed", "verified"];
@@ -135,6 +148,11 @@ export declare const LISTED_REVIEW_STATUSES: readonly ["listed", "verified"];
135
148
  * Listed section's own `reviewStatus in [...]` steps aside for a Status
136
149
  * clause that names one of its two values, and a clause naming any other
137
150
  * value leaves the section empty by definition — `null`, never queried.
151
+ *
152
+ * The Starters section is live site starter listings (AGL-4049): those
153
+ * waiting for review, or, once the Status select names a status or Taken
154
+ * down, those the select names in their place. It holds every status, so it
155
+ * is never empty by definition.
138
156
  */
139
157
  export declare function reviewQueueBase(section: ReviewQueueSection, clauses: readonly ListFilterClause[]): ListQueryFilter[] | null;
140
158
  /** The queue's Status select as the clause it asks for, or none for "All". */
@@ -296,6 +296,33 @@ export const BROWSE_SORTS = {
296
296
  tokensPath: LISTING_NAME_TOKENS
297
297
  }
298
298
  };
299
+ /**
300
+ * The Starters section's query (AGL-4049): the same Status select and search
301
+ * as the plugin sections, read newest first. The plugin sections read in id
302
+ * order, which equality alone serves; this one pairs each field it can ask
303
+ * with `createdAt` descending, the composites the index file carries for it.
304
+ */ export const STARTER_REVIEW_QUEUE_QUERY = {
305
+ fields: REVIEW_QUEUE_QUERY.fields,
306
+ sorts: [
307
+ {
308
+ path: 'createdAt',
309
+ direction: 'desc'
310
+ }
311
+ ],
312
+ search: REVIEW_QUEUE_QUERY.search
313
+ };
314
+ /** What the Starters section shows while the Status select says All: the starters waiting for review. */ export const STARTER_AWAITING_REVIEW_STATUS = 'submitted';
315
+ /** The predicates the Starters section can scope by, for its index enumeration. */ export const STARTER_REVIEW_QUEUE_BASE_PATHS = [
316
+ {
317
+ path: 'artifactType'
318
+ },
319
+ {
320
+ path: 'deletedAt'
321
+ },
322
+ {
323
+ path: 'reviewStatus'
324
+ }
325
+ ];
299
326
  /** A listing's newest bytes have no approval: the `latestVersionReviewState` values that mean it. */ export const AWAITING_REVIEW_STATES = [
300
327
  'pending',
301
328
  'rejected'
@@ -309,7 +336,35 @@ export const LISTED_REVIEW_STATUSES = [
309
336
  * Listed section's own `reviewStatus in [...]` steps aside for a Status
310
337
  * clause that names one of its two values, and a clause naming any other
311
338
  * value leaves the section empty by definition — `null`, never queried.
339
+ *
340
+ * The Starters section is live site starter listings (AGL-4049): those
341
+ * waiting for review, or, once the Status select names a status or Taken
342
+ * down, those the select names in their place. It holds every status, so it
343
+ * is never empty by definition.
312
344
  */ export function reviewQueueBase(section, clauses) {
345
+ if (section === 'starters') {
346
+ const starters = [
347
+ {
348
+ path: 'artifactType',
349
+ op: '==',
350
+ value: 'siteStarter'
351
+ },
352
+ {
353
+ path: 'deletedAt',
354
+ op: '==',
355
+ value: null
356
+ }
357
+ ];
358
+ const narrowed = clauses.some((clause)=>clause.field === 'reviewStatus' && clause.value || clause.field === LISTING_TAKEN_DOWN);
359
+ if (!narrowed) {
360
+ starters.push({
361
+ path: 'reviewStatus',
362
+ op: '==',
363
+ value: STARTER_AWAITING_REVIEW_STATUS
364
+ });
365
+ }
366
+ return starters;
367
+ }
313
368
  const base = [
314
369
  {
315
370
  path: 'type',