@storyshelf/app 0.6.1 → 0.7.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.
- package/README.md +42 -4
- package/dist/app-types.d.mts +1 -3
- package/dist/asset-manifest.mjs +13 -5
- package/dist/asset-manifest.mjs.map +1 -1
- package/dist/assets/{htmx.min.js → htmx.mjs} +10 -1
- package/dist/assets/htmx.mjs.map +1 -0
- package/dist/assets/mark.mjs +10 -0
- package/dist/assets/mark.mjs.map +1 -0
- package/dist/boot-tasks.mjs +80 -0
- package/dist/boot-tasks.mjs.map +1 -0
- package/dist/build-state-flags.mjs +18 -0
- package/dist/build-state-flags.mjs.map +1 -0
- package/dist/build-state-setup.mjs +75 -0
- package/dist/build-state-setup.mjs.map +1 -0
- package/dist/capture-alerts.mjs +46 -0
- package/dist/capture-alerts.mjs.map +1 -0
- package/dist/capture-process.mjs +59 -0
- package/dist/capture-process.mjs.map +1 -0
- package/dist/capture-setup.mjs +95 -117
- package/dist/capture-setup.mjs.map +1 -1
- package/dist/connections.mjs +34 -0
- package/dist/connections.mjs.map +1 -0
- package/dist/consumer-setup.mjs +58 -0
- package/dist/consumer-setup.mjs.map +1 -0
- package/dist/credentials.mjs +3 -2
- package/dist/credentials.mjs.map +1 -1
- package/dist/git-status.mjs +46 -0
- package/dist/git-status.mjs.map +1 -0
- package/dist/hidden-data.mjs +24 -0
- package/dist/hidden-data.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +202 -124
- package/dist/index.mjs.map +1 -1
- package/dist/insights/budget.mjs +49 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/deps.mjs +21 -0
- package/dist/insights/deps.mjs.map +1 -0
- package/dist/insights/errors.mjs +22 -0
- package/dist/insights/errors.mjs.map +1 -0
- package/dist/insights/health-panel.mjs +40 -0
- package/dist/insights/health-panel.mjs.map +1 -0
- package/dist/insights/job.mjs +103 -0
- package/dist/insights/job.mjs.map +1 -0
- package/dist/insights/load-health.mjs +59 -0
- package/dist/insights/load-health.mjs.map +1 -0
- package/dist/insights/load-triage.mjs +98 -0
- package/dist/insights/load-triage.mjs.map +1 -0
- package/dist/insights/panel.mjs +28 -0
- package/dist/insights/panel.mjs.map +1 -0
- package/dist/insights/request.mjs +113 -0
- package/dist/insights/request.mjs.map +1 -0
- package/dist/insights/stale.mjs +21 -0
- package/dist/insights/stale.mjs.map +1 -0
- package/dist/insights/usage-report.mjs +60 -0
- package/dist/insights/usage-report.mjs.map +1 -0
- package/dist/insights/view.mjs +48 -0
- package/dist/insights/view.mjs.map +1 -0
- package/dist/job-canceller.mjs +14 -0
- package/dist/job-canceller.mjs.map +1 -0
- package/dist/job-queue.mjs +51 -0
- package/dist/job-queue.mjs.map +1 -0
- package/dist/jobs-setup.mjs +132 -0
- package/dist/jobs-setup.mjs.map +1 -0
- package/dist/lifecycle.mjs +109 -19
- package/dist/lifecycle.mjs.map +1 -1
- package/dist/middleware/auth-gate.mjs +4 -2
- package/dist/middleware/auth-gate.mjs.map +1 -1
- package/dist/middleware/rate-limit.mjs +41 -33
- package/dist/middleware/rate-limit.mjs.map +1 -1
- package/dist/middleware/request-log.mjs +3 -2
- package/dist/middleware/request-log.mjs.map +1 -1
- package/dist/middleware/require-review.mjs +51 -0
- package/dist/middleware/require-review.mjs.map +1 -0
- package/dist/middleware/require-testing.mjs +41 -0
- package/dist/middleware/require-testing.mjs.map +1 -0
- package/dist/middleware/setup-gate.mjs.map +1 -1
- package/dist/middleware/store-scope.mjs +12 -2
- package/dist/middleware/store-scope.mjs.map +1 -1
- package/dist/notifications-pause.mjs +33 -0
- package/dist/notifications-pause.mjs.map +1 -0
- package/dist/notify.d.mts +2 -1
- package/dist/notify.mjs +78 -39
- package/dist/notify.mjs.map +1 -1
- package/dist/pages/a11y-violations.mjs +116 -0
- package/dist/pages/a11y-violations.mjs.map +1 -0
- package/dist/pages/admin-ai.mjs +78 -0
- package/dist/pages/admin-ai.mjs.map +1 -0
- package/dist/pages/admin-system-origin.mjs +123 -0
- package/dist/pages/admin-system-origin.mjs.map +1 -0
- package/dist/pages/admin-system-targets.mjs +53 -0
- package/dist/pages/admin-system-targets.mjs.map +1 -0
- package/dist/pages/admin-system.mjs +63 -13
- package/dist/pages/admin-system.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +104 -76
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff-actions.mjs +87 -33
- package/dist/pages/build-diff-actions.mjs.map +1 -1
- package/dist/pages/build-diff-comments.mjs +16 -14
- package/dist/pages/build-diff-comments.mjs.map +1 -1
- package/dist/pages/build-diff-environment.mjs +82 -0
- package/dist/pages/build-diff-environment.mjs.map +1 -0
- package/dist/pages/build-diff-header.mjs +22 -4
- package/dist/pages/build-diff-header.mjs.map +1 -1
- package/dist/pages/build-diff-insight.mjs +113 -0
- package/dist/pages/build-diff-insight.mjs.map +1 -0
- package/dist/pages/build-diff-model.mjs +55 -10
- package/dist/pages/build-diff-model.mjs.map +1 -1
- package/dist/pages/build-diff-nav.mjs +28 -12
- package/dist/pages/build-diff-nav.mjs.map +1 -1
- package/dist/pages/build-diff-shortcuts.mjs +5 -1
- package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
- package/dist/pages/build-diff-usage.mjs +63 -0
- package/dist/pages/build-diff-usage.mjs.map +1 -0
- package/dist/pages/build-diff-viewer.mjs +62 -5
- package/dist/pages/build-diff-viewer.mjs.map +1 -1
- package/dist/pages/build-diff.mjs +34 -13
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/build-tests.mjs +74 -0
- package/dist/pages/build-tests.mjs.map +1 -0
- package/dist/pages/component-detail.mjs +249 -0
- package/dist/pages/component-detail.mjs.map +1 -0
- package/dist/pages/component-stories.mjs +168 -0
- package/dist/pages/component-stories.mjs.map +1 -0
- package/dist/pages/compute-jobs.mjs +13 -26
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/design-links.mjs +182 -0
- package/dist/pages/design-links.mjs.map +1 -0
- package/dist/pages/environment-change.mjs +53 -0
- package/dist/pages/environment-change.mjs.map +1 -0
- package/dist/pages/health-panel.mjs +126 -0
- package/dist/pages/health-panel.mjs.map +1 -0
- package/dist/pages/library-grid.mjs +105 -0
- package/dist/pages/library-grid.mjs.map +1 -0
- package/dist/pages/library-model.mjs +47 -0
- package/dist/pages/library-model.mjs.map +1 -0
- package/dist/pages/library-snapshots.mjs +130 -0
- package/dist/pages/library-snapshots.mjs.map +1 -0
- package/dist/pages/library.mjs +112 -218
- package/dist/pages/library.mjs.map +1 -1
- package/dist/pages/notifications-paused.mjs +22 -0
- package/dist/pages/notifications-paused.mjs.map +1 -0
- package/dist/pages/profile.mjs +53 -48
- package/dist/pages/profile.mjs.map +1 -1
- package/dist/pages/project-builds-table.mjs +2 -2
- package/dist/pages/project-builds-table.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +51 -21
- package/dist/pages/project-builds.mjs.map +1 -1
- package/dist/pages/project-settings.mjs +51 -12
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects-card.mjs +14 -1
- package/dist/pages/projects-card.mjs.map +1 -1
- package/dist/pages/projects.mjs +6 -2
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/review-changeset.mjs +116 -0
- package/dist/pages/review-changeset.mjs.map +1 -0
- package/dist/pages/review-comments.mjs +103 -0
- package/dist/pages/review-comments.mjs.map +1 -0
- package/dist/pages/review-verdict.mjs +102 -0
- package/dist/pages/review-verdict.mjs.map +1 -0
- package/dist/pages/settings-ai.mjs +69 -0
- package/dist/pages/settings-ai.mjs.map +1 -0
- package/dist/pages/settings-capture.mjs +124 -0
- package/dist/pages/settings-capture.mjs.map +1 -0
- package/dist/pages/settings-design-credential.mjs +99 -0
- package/dist/pages/settings-design-credential.mjs.map +1 -0
- package/dist/pages/settings-design-links.mjs +179 -0
- package/dist/pages/settings-design-links.mjs.map +1 -0
- package/dist/pages/settings-design.mjs +48 -0
- package/dist/pages/settings-design.mjs.map +1 -0
- package/dist/pages/settings-general.mjs +46 -21
- package/dist/pages/settings-general.mjs.map +1 -1
- package/dist/pages/settings-labels.mjs +43 -2
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-links.mjs +112 -0
- package/dist/pages/settings-links.mjs.map +1 -0
- package/dist/pages/settings-notifications-git.mjs +128 -0
- package/dist/pages/settings-notifications-git.mjs.map +1 -0
- package/dist/pages/settings-notifications.mjs +79 -42
- package/dist/pages/settings-notifications.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +33 -2
- package/dist/pages/settings-tests.mjs.map +1 -1
- package/dist/pages/target-choices.mjs +39 -0
- package/dist/pages/target-choices.mjs.map +1 -0
- package/dist/publish-setup.mjs +89 -0
- package/dist/publish-setup.mjs.map +1 -0
- package/dist/retention-timer.mjs +2 -1
- package/dist/retention-timer.mjs.map +1 -1
- package/dist/review-events.mjs +48 -0
- package/dist/review-events.mjs.map +1 -0
- package/dist/routers/admin-pages.mjs +45 -3
- package/dist/routers/admin-pages.mjs.map +1 -1
- package/dist/routers/admin-public-origin.mjs +85 -0
- package/dist/routers/admin-public-origin.mjs.map +1 -0
- package/dist/routers/assets.mjs +9 -2
- package/dist/routers/assets.mjs.map +1 -1
- package/dist/routers/auth.mjs +2 -2
- package/dist/routers/auth.mjs.map +1 -1
- package/dist/routers/baseline-guard.mjs +2 -2
- package/dist/routers/baseline-guard.mjs.map +1 -1
- package/dist/routers/baselines.handlers.mjs +64 -0
- package/dist/routers/baselines.handlers.mjs.map +1 -0
- package/dist/routers/baselines.mjs +125 -0
- package/dist/routers/baselines.mjs.map +1 -0
- package/dist/routers/build-cancel.mjs +68 -0
- package/dist/routers/build-cancel.mjs.map +1 -0
- package/dist/routers/build-changeset.mjs +70 -0
- package/dist/routers/build-changeset.mjs.map +1 -0
- package/dist/routers/build-reviews.handlers.mjs +86 -0
- package/dist/routers/build-reviews.handlers.mjs.map +1 -0
- package/dist/routers/build-reviews.mjs +74 -0
- package/dist/routers/build-reviews.mjs.map +1 -0
- package/dist/routers/builds.handlers.mjs +46 -55
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +71 -17
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/capture-config.mjs +35 -0
- package/dist/routers/capture-config.mjs.map +1 -0
- package/dist/routers/comments.mjs +41 -15
- package/dist/routers/comments.mjs.map +1 -1
- package/dist/routers/component-catalog.handlers.mjs +71 -0
- package/dist/routers/component-catalog.handlers.mjs.map +1 -0
- package/dist/routers/component.handlers.mjs +121 -0
- package/dist/routers/component.handlers.mjs.map +1 -0
- package/dist/routers/connections.mjs +118 -0
- package/dist/routers/connections.mjs.map +1 -0
- package/dist/routers/design-connect.handlers.mjs +44 -0
- package/dist/routers/design-connect.handlers.mjs.map +1 -0
- package/dist/routers/design.handlers.mjs +114 -0
- package/dist/routers/design.handlers.mjs.map +1 -0
- package/dist/routers/health-report.mjs +2 -3
- package/dist/routers/health-report.mjs.map +1 -1
- package/dist/routers/health.mjs +4 -3
- package/dist/routers/health.mjs.map +1 -1
- package/dist/routers/helpers.mjs +19 -3
- package/dist/routers/helpers.mjs.map +1 -1
- package/dist/routers/insights-ui.mjs +64 -0
- package/dist/routers/insights-ui.mjs.map +1 -0
- package/dist/routers/insights.handlers.mjs +45 -0
- package/dist/routers/insights.handlers.mjs.map +1 -0
- package/dist/routers/insights.mjs +189 -0
- package/dist/routers/insights.mjs.map +1 -0
- package/dist/routers/links.handlers.mjs +80 -0
- package/dist/routers/links.handlers.mjs.map +1 -0
- package/dist/routers/links.mjs +139 -0
- package/dist/routers/links.mjs.map +1 -0
- package/dist/routers/media.mjs +18 -4
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs +2 -1
- package/dist/routers/members.mjs.map +1 -1
- package/dist/routers/notification-channels.mjs +17 -23
- package/dist/routers/notification-channels.mjs.map +1 -1
- package/dist/routers/oauth-state.mjs +45 -0
- package/dist/routers/oauth-state.mjs.map +1 -0
- package/dist/routers/preview-base.mjs +38 -0
- package/dist/routers/preview-base.mjs.map +1 -0
- package/dist/routers/preview-token.mjs +51 -0
- package/dist/routers/preview-token.mjs.map +1 -0
- package/dist/routers/profile.mjs +4 -2
- package/dist/routers/profile.mjs.map +1 -1
- package/dist/routers/projects.mjs +17 -1
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/publish-upload.mjs +78 -0
- package/dist/routers/publish-upload.mjs.map +1 -0
- package/dist/routers/published-access.mjs +52 -0
- package/dist/routers/published-access.mjs.map +1 -0
- package/dist/routers/published-files.mjs +52 -0
- package/dist/routers/published-files.mjs.map +1 -0
- package/dist/routers/review-forms.mjs +72 -0
- package/dist/routers/review-forms.mjs.map +1 -0
- package/dist/routers/review-section.handlers.mjs +85 -0
- package/dist/routers/review-section.handlers.mjs.map +1 -0
- package/dist/routers/review-switch-off.mjs +29 -0
- package/dist/routers/review-switch-off.mjs.map +1 -0
- package/dist/routers/schemas.mjs +140 -22
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/scoped-models.mjs +22 -0
- package/dist/routers/scoped-models.mjs.map +1 -0
- package/dist/routers/settings-ai.mjs +35 -0
- package/dist/routers/settings-ai.mjs.map +1 -0
- package/dist/routers/settings-capture.mjs +77 -0
- package/dist/routers/settings-capture.mjs.map +1 -0
- package/dist/routers/settings-design.mjs +103 -0
- package/dist/routers/settings-design.mjs.map +1 -0
- package/dist/routers/settings-general.mjs +55 -10
- package/dist/routers/settings-general.mjs.map +1 -1
- package/dist/routers/settings-git-channel.mjs +77 -0
- package/dist/routers/settings-git-channel.mjs.map +1 -0
- package/dist/routers/settings-labels.mjs +27 -1
- package/dist/routers/settings-labels.mjs.map +1 -1
- package/dist/routers/settings-links.mjs +63 -0
- package/dist/routers/settings-links.mjs.map +1 -0
- package/dist/routers/settings-members.mjs +4 -7
- package/dist/routers/settings-members.mjs.map +1 -1
- package/dist/routers/settings-notifications.mjs +15 -10
- package/dist/routers/settings-notifications.mjs.map +1 -1
- package/dist/routers/settings-tokens.mjs +2 -5
- package/dist/routers/settings-tokens.mjs.map +1 -1
- package/dist/routers/settings-webhooks.mjs +7 -24
- package/dist/routers/settings-webhooks.mjs.map +1 -1
- package/dist/routers/settings.handlers.mjs +107 -26
- package/dist/routers/settings.handlers.mjs.map +1 -1
- package/dist/routers/settings.mjs +16 -4
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/signed-storybook.mjs +53 -0
- package/dist/routers/signed-storybook.mjs.map +1 -0
- package/dist/routers/snapshot-groups.mjs +63 -0
- package/dist/routers/snapshot-groups.mjs.map +1 -0
- package/dist/routers/snapshots.mjs +103 -5
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/storybook.mjs +37 -88
- package/dist/routers/storybook.mjs.map +1 -1
- package/dist/routers/tokens.mjs +2 -5
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +34 -5
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +5 -11
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/routers/well-known.mjs +11 -4
- package/dist/routers/well-known.mjs.map +1 -1
- package/dist/routes/api-routes.mjs +93 -0
- package/dist/routes/api-routes.mjs.map +1 -0
- package/dist/routes/classification.mjs +38 -0
- package/dist/routes/classification.mjs.map +1 -0
- package/dist/routes/openapi-classes.mjs +22 -0
- package/dist/routes/openapi-classes.mjs.map +1 -0
- package/dist/routes/page-routes.mjs +104 -0
- package/dist/routes/page-routes.mjs.map +1 -0
- package/dist/runtime.mjs +18 -13
- package/dist/runtime.mjs.map +1 -1
- package/dist/serverless.mjs +5 -0
- package/dist/store.mjs +19 -1
- package/dist/store.mjs.map +1 -1
- package/dist/targets.mjs +46 -0
- package/dist/targets.mjs.map +1 -0
- package/dist/ui/buttons.mjs +16 -3
- package/dist/ui/buttons.mjs.map +1 -1
- package/dist/ui/design-reference.mjs +97 -0
- package/dist/ui/design-reference.mjs.map +1 -0
- package/dist/ui/document.mjs +1 -2
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/forms.mjs +6 -5
- package/dist/ui/forms.mjs.map +1 -1
- package/dist/ui/icons/paths.mjs +1 -0
- package/dist/ui/icons/paths.mjs.map +1 -1
- package/dist/ui/preview-embed.mjs +82 -0
- package/dist/ui/preview-embed.mjs.map +1 -0
- package/dist/ui/shell/client-script.mjs +85 -0
- package/dist/ui/shell/client-script.mjs.map +1 -1
- package/dist/ui/shell/nav.mjs +12 -8
- package/dist/ui/shell/nav.mjs.map +1 -1
- package/dist/ui/shell/review-script.mjs +8 -0
- package/dist/ui/shell/review-script.mjs.map +1 -1
- package/dist/ui/shell/sidebar.mjs +2 -2
- package/dist/ui/shell/sidebar.mjs.map +1 -1
- package/package.json +11 -6
- package/dist/asset-paths.mjs +0 -23
- package/dist/asset-paths.mjs.map +0 -1
- package/dist/assets/mark.svg +0 -1
- package/dist/pages/settings-status.mjs +0 -137
- package/dist/pages/settings-status.mjs.map +0 -1
- package/dist/routers/settings-status.mjs +0 -77
- package/dist/routers/settings-status.mjs.map +0 -1
- package/dist/routers/status-configs.mjs +0 -108
- package/dist/routers/status-configs.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-system.mjs","names":[],"sources":["../../src/pages/admin-system.tsx"],"sourcesContent":["import type { ShelfConfig } from \"@storyshelf/core/config\";\nimport type { CredentialProbe } from \"@storyshelf/core/models\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { HealthReport } from \"../routers/health-report.ts\";\nimport {\n Alert,\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Stat,\n Table,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Data for the site-admin System page (adapters, health, safe config). */\nexport interface AdminSystemData {\n report: HealthReport;\n authEnabled: boolean;\n config: ShelfConfig;\n /** Active login method labels (read-only badges; empty when auth is off). */\n authMethods?: string[];\n /** Stored-credential state; null/undefined when no secret is configured. */\n credentials?: CredentialProbe | null;\n}\n\nfunction healthTone(state: string): \"success\" | \"info\" | \"danger\" | \"neutral\" {\n if (state === \"ok\") {\n return \"success\";\n }\n if (state === \"starting\") {\n return \"info\";\n }\n if (state === \"failed\" || state === \"degraded\") {\n return \"danger\";\n }\n return \"neutral\";\n}\n\nfunction formatUptime(uptimeSecs: number): string {\n if (uptimeSecs < 60) {\n return `${uptimeSecs}s`;\n }\n const minutes = Math.floor(uptimeSecs / 60);\n if (minutes < 60) {\n return `${minutes}m ${uptimeSecs % 60}s`;\n }\n return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;\n}\n\nfunction formatLatency(latencyMs: number | undefined): string {\n return latencyMs === undefined ? \"—\" : `${latencyMs} ms`;\n}\n\nfunction formatOptional(value: string | number | null | undefined): string {\n if (value === undefined || value === null || value === \"\") {\n return \"—\";\n }\n return String(value);\n}\n\n/** One adapter row: identity, version, live probe state, latency, detail. */\nfunction AdapterRow(props: {\n adapter: HealthReport[\"adapters\"][number];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { adapter } = props;\n return (\n <tr key={`${adapter.category}/${adapter.kind}`}>\n <td>\n <strong>{adapter.name}</strong>\n {adapter.description ? <Meta as=\"div\">{adapter.description}</Meta> : null}\n {adapter.hasLifecycle ? null : <Meta as=\"div\">No lifecycle hooks</Meta>}\n </td>\n <td>\n <Meta as=\"span\">{adapter.category}</Meta>\n </td>\n <td>\n <Meta as=\"span\" mono>\n {adapter.kind}\n </Meta>\n </td>\n <td>\n <Meta as=\"span\" mono>\n {adapter.version}\n </Meta>\n </td>\n <td>\n <Badge tone={healthTone(adapter.state)} icon>\n {adapter.state}\n </Badge>\n </td>\n <td class=\"nowrap\">\n <Meta as=\"span\">{formatLatency(adapter.latencyMs)}</Meta>\n </td>\n <td>\n <Meta as=\"span\">{adapter.detail ?? \"—\"}</Meta>\n </td>\n </tr>\n );\n}\n\ninterface AdapterTableProps {\n report: HealthReport;\n}\n\n/** Adapter inventory with live health states. */\nfunction AdapterTable(props: AdapterTableProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { report } = props;\n if (report.adapters.length === 0) {\n return <EmptyState description=\"No adapters wired.\" />;\n }\n return (\n <Table>\n <table>\n <thead>\n <tr>\n <th>Adapter</th>\n <th>Category</th>\n <th>Kind</th>\n <th>Version</th>\n <th>Health</th>\n <th>Latency</th>\n <th>Detail</th>\n </tr>\n </thead>\n <tbody>\n {report.adapters.map((adapter) => (\n <AdapterRow key={`${adapter.category}/${adapter.kind}`} adapter={adapter} />\n ))}\n </tbody>\n </table>\n </Table>\n );\n}\n\ninterface ServerFactsProps {\n report: HealthReport;\n authEnabled: boolean;\n config: ShelfConfig;\n authMethods?: string[];\n}\n\n/** Non-secret server facts (never secret, admin token, or credentials). */\nfunction ServerFacts(props: ServerFactsProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { report, authEnabled, config, authMethods } = props;\n return (\n <Card>\n <SectionTitle>Server</SectionTitle>\n <div class=\"grid grid--3\">\n <Stat label=\"Status\" value={report.status} />\n <Stat label=\"Uptime\" value={formatUptime(report.uptimeSecs)} />\n <Stat label=\"Version\" value={report.version} />\n </div>\n <Meta>\n Auth {authEnabled ? \"enabled\" : \"disabled — all operations permitted\"} · Capture concurrency{\" \"}\n {formatOptional(config.captureConcurrency)} · Purge TTL{\" \"}\n {formatOptional(config.purgeTtlDays)} days · Branch TTL{\" \"}\n {config.branchTtlDays === null\n ? \"disabled\"\n : `${formatOptional(config.branchTtlDays)} days`}\n </Meta>\n {authMethods && authMethods.length > 0 ? (\n <HStack>\n {authMethods.map((method) => (\n <Badge key={method}>{method}</Badge>\n ))}\n </HStack>\n ) : null}\n <Meta>\n Scratch dir {formatOptional(config.scratchDir)} · Published domain{\" \"}\n {formatOptional(config.publishedBaseDomain)} · Max upload{\" \"}\n {formatOptional(config.maxUploadBytes)} bytes\n </Meta>\n </Card>\n );\n}\n\n/** Credential encryption state with the admin-only re-encrypt action. */\nfunction CredentialsCard(props: {\n probe: CredentialProbe;\n config: ShelfConfig;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { probe, config } = props;\n const rotating = Boolean(config.previousSecret);\n return (\n <Card>\n <SectionTitle>Stored credentials</SectionTitle>\n <Meta>\n Webhook secrets, git tokens and notification secrets are encrypted with the server secret.\n </Meta>\n <div class=\"grid grid--3\">\n <Stat label=\"Current secret\" value={String(probe.current)} />\n <Stat label=\"Previous secret\" value={String(probe.previous)} />\n <Stat label=\"Unreadable\" value={String(probe.unreadable.length)} />\n </div>\n {probe.unreadable.length > 0 ? (\n <Alert tone=\"danger\" title=\"Some credentials cannot be decrypted\">\n No configured secret matches {probe.unreadable.length} stored credential(s) (\n {[...new Set(probe.unreadable.map((row) => row.source))].join(\", \")}). Check that SECRET\n and SECRET_PREVIOUS are correct, or recreate them.\n </Alert>\n ) : null}\n {rotating && probe.previous > 0 ? (\n <form\n method=\"post\"\n action=\"/admin/credentials/reencrypt\"\n hx-post=\"/admin/credentials/reencrypt\"\n hx-target=\"body\"\n >\n {csrfField()}\n <Button type=\"submit\" variant=\"secondary\" size=\"sm\">\n Re-encrypt with current secret\n </Button>\n </form>\n ) : (\n <Meta>\n {rotating\n ? \"All credentials use the current secret. Remove SECRET_PREVIOUS.\"\n : \"To rotate, set SECRET to the new value and SECRET_PREVIOUS to the old one.\"}\n </Meta>\n )}\n </Card>\n );\n}\n\n/** Site-admin System page: adapter inventory with in-depth health. */\nexport function renderAdminSystemPage(data: AdminSystemData): RenderedContent {\n const { report, authEnabled, config, authMethods, credentials } = data;\n return (\n <DocumentLayout title=\"System\" nav={{ active: \"admin\" }}>\n <PageHeader\n title=\"System\"\n description=\"Adapter inventory with in-depth health. Visible to site admins only.\"\n meta={\n <>\n {report.adapters.length} adapters · {report.status} · up{\" \"}\n {formatUptime(report.uptimeSecs)}\n </>\n }\n actions={\n <Button variant=\"secondary\" size=\"sm\" href=\"/admin\">\n Recheck\n </Button>\n }\n />\n <ServerFacts\n report={report}\n authEnabled={authEnabled}\n config={config}\n authMethods={authMethods}\n />\n {credentials ? (\n <div class=\"mt-1\">\n <CredentialsCard probe={credentials} config={config} />\n </div>\n ) : null}\n <div class=\"mt-1\">\n <Card>\n <SectionTitle>Adapters</SectionTitle>\n <Meta>\n Live probe per adapter (2s timeout, parallel). Setup failures surface as failed rows;\n adapters without lifecycle hooks report their wired state.\n </Meta>\n <AdapterTable report={report} />\n </Card>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin-system.mjs","names":[],"sources":["../../src/pages/admin-system.tsx"],"sourcesContent":["import type { RunnerTarget } from \"@storyshelf/core/adapter/capture-runner\";\nimport type { ShelfConfig } from \"@storyshelf/core/config\";\nimport type { CredentialProbe } from \"@storyshelf/core/models\";\nimport type { CaptureHostSummary } from \"@storyshelf/core/utils\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { HealthReport } from \"../routers/health-report.ts\";\nimport {\n Alert,\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Stat,\n Table,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { PublicOriginCard, type PublicOriginView } from \"./admin-system-origin.tsx\";\nimport { CaptureTargetsCard } from \"./admin-system-targets.tsx\";\nimport { NotificationsPausedNotice } from \"./notifications-paused.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Data for the site-admin System page (adapters, health, safe config). */\nexport interface AdminSystemData {\n report: HealthReport;\n authEnabled: boolean;\n config: ShelfConfig;\n /** Active login method labels (read-only badges; empty when auth is off). */\n authMethods?: string[];\n /** Stored-credential state; null/undefined when no secret is configured. */\n credentials?: CredentialProbe | null;\n /** True when AI is configured (shows the AI usage link). */\n aiEnabled?: boolean;\n /** Whether `options.testing` is set; false shows the hosting-only notice. */\n testing?: boolean;\n /** Snapshots hidden because `testing` is off (only meaningful when it is). */\n hiddenSnapshots?: number;\n /** Detected host budget and effective capture concurrency; null when capture runs elsewhere. */\n capture?: CaptureHostSummary | null;\n /** The browsers the capture runner offers (ADR 0007); omitted without a runner list. */\n targets?: readonly RunnerTarget[] | undefined;\n /** The public origin and where it came from; omitted when the store has no resolver. */\n publicOrigin?: PublicOriginView | undefined;\n /** Notifiers are wired but no public origin resolves, so nothing is sent (ADR 0014, decision 11). */\n notificationsPaused?: boolean | undefined;\n}\n\nfunction healthTone(state: string): \"success\" | \"info\" | \"danger\" | \"neutral\" {\n if (state === \"ok\") {\n return \"success\";\n }\n if (state === \"starting\") {\n return \"info\";\n }\n if (state === \"failed\" || state === \"degraded\") {\n return \"danger\";\n }\n return \"neutral\";\n}\n\nfunction formatUptime(uptimeSecs: number): string {\n if (uptimeSecs < 60) {\n return `${uptimeSecs}s`;\n }\n const minutes = Math.floor(uptimeSecs / 60);\n if (minutes < 60) {\n return `${minutes}m ${uptimeSecs % 60}s`;\n }\n return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;\n}\n\nfunction formatLatency(latencyMs: number | undefined): string {\n return latencyMs === undefined ? \"—\" : `${latencyMs} ms`;\n}\n\nfunction formatOptional(value: string | number | null | undefined): string {\n if (value === undefined || value === null || value === \"\") {\n return \"—\";\n }\n return String(value);\n}\n\n/** One adapter row: identity, version, live probe state, latency, detail. */\nfunction AdapterRow(props: {\n adapter: HealthReport[\"adapters\"][number];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { adapter } = props;\n return (\n <tr key={`${adapter.category}/${adapter.kind}`}>\n <td>\n <strong>{adapter.name}</strong>\n {adapter.description ? <Meta as=\"div\">{adapter.description}</Meta> : null}\n {adapter.hasLifecycle ? null : <Meta as=\"div\">No lifecycle hooks</Meta>}\n </td>\n <td>\n <Meta as=\"span\">{adapter.category}</Meta>\n </td>\n <td>\n <Meta as=\"span\" mono>\n {adapter.kind}\n </Meta>\n </td>\n <td>\n <Meta as=\"span\" mono>\n {adapter.version}\n </Meta>\n </td>\n <td>\n <Badge tone={healthTone(adapter.state)} icon>\n {adapter.state}\n </Badge>\n </td>\n <td class=\"nowrap\">\n <Meta as=\"span\">{formatLatency(adapter.latencyMs)}</Meta>\n </td>\n <td>\n <Meta as=\"span\">{adapter.detail ?? \"—\"}</Meta>\n </td>\n </tr>\n );\n}\n\ninterface AdapterTableProps {\n report: HealthReport;\n}\n\n/** Adapter inventory with live health states. */\nfunction AdapterTable(props: AdapterTableProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { report } = props;\n if (report.adapters.length === 0) {\n return <EmptyState description=\"No adapters wired.\" />;\n }\n return (\n <Table>\n <table>\n <thead>\n <tr>\n <th>Adapter</th>\n <th>Category</th>\n <th>Kind</th>\n <th>Version</th>\n <th>Health</th>\n <th>Latency</th>\n <th>Detail</th>\n </tr>\n </thead>\n <tbody>\n {report.adapters.map((adapter) => (\n <AdapterRow key={`${adapter.category}/${adapter.kind}`} adapter={adapter} />\n ))}\n </tbody>\n </table>\n </Table>\n );\n}\n\n/** Detected hardware and the capture concurrency it yields (auto unless configured). */\nfunction CaptureSizing(props: {\n capture: CaptureHostSummary;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { cpus, memoryMb, builds, pages } = props.capture;\n return (\n <Meta>\n Capture host {cpus} CPU · {memoryMb} MB · builds at once {builds.value}\n {builds.auto ? \" (auto)\" : \" (configured)\"} · pages per build {pages.start}–{pages.max} (auto,\n adapts to load unless the runner pins it)\n </Meta>\n );\n}\n\ninterface ServerFactsProps {\n data: AdminSystemData;\n}\n\n/** Non-secret server facts (never secret, admin token, or credentials). */\nfunction ServerFacts(props: ServerFactsProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { report, authEnabled, config, authMethods, capture } = props.data;\n return (\n <Card>\n <SectionTitle>Server</SectionTitle>\n <div class=\"grid grid--3\">\n <Stat label=\"Status\" value={report.status} />\n <Stat label=\"Uptime\" value={formatUptime(report.uptimeSecs)} />\n <Stat label=\"Version\" value={report.version} />\n </div>\n <Meta>\n Auth {authEnabled ? \"enabled\" : \"disabled — all operations permitted\"} · Purge TTL{\" \"}\n {formatOptional(config.purgeTtlDays)} days · Branch TTL{\" \"}\n {config.branchTtlDays === null\n ? \"disabled\"\n : `${formatOptional(config.branchTtlDays)} days`}\n </Meta>\n {authMethods && authMethods.length > 0 ? (\n <HStack>\n {authMethods.map((method) => (\n <Badge key={method}>{method}</Badge>\n ))}\n </HStack>\n ) : null}\n {capture ? <CaptureSizing capture={capture} /> : null}\n <Meta>\n Scratch dir {formatOptional(config.scratchDir)} · Published domain{\" \"}\n {formatOptional(config.publishedBaseDomain)} · Max upload{\" \"}\n {formatOptional(config.maxUploadBytes)} bytes\n </Meta>\n </Card>\n );\n}\n\n/** Credential encryption state with the admin-only re-encrypt action. */\nfunction CredentialsCard(props: {\n probe: CredentialProbe;\n config: ShelfConfig;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { probe, config } = props;\n const rotating = Boolean(config.previousSecret);\n return (\n <Card>\n <SectionTitle>Stored credentials</SectionTitle>\n <Meta>\n Webhook secrets, git tokens and notification secrets are encrypted with the server secret.\n </Meta>\n <div class=\"grid grid--3\">\n <Stat label=\"Current secret\" value={String(probe.current)} />\n <Stat label=\"Previous secret\" value={String(probe.previous)} />\n <Stat label=\"Unreadable\" value={String(probe.unreadable.length)} />\n </div>\n {probe.unreadable.length > 0 ? (\n <Alert tone=\"danger\" title=\"Some credentials cannot be decrypted\">\n No configured secret matches {probe.unreadable.length} stored credential(s) (\n {[...new Set(probe.unreadable.map((row) => row.source))].join(\", \")}). Check that SECRET\n and SECRET_PREVIOUS are correct, or recreate them.\n </Alert>\n ) : null}\n {rotating && probe.previous > 0 ? (\n <form\n method=\"post\"\n action=\"/admin/credentials/reencrypt\"\n hx-post=\"/admin/credentials/reencrypt\"\n hx-target=\"body\"\n >\n {csrfField()}\n <Button type=\"submit\" variant=\"secondary\" size=\"sm\">\n Re-encrypt with current secret\n </Button>\n </form>\n ) : (\n <Meta>\n {rotating\n ? \"All credentials use the current secret. Remove SECRET_PREVIOUS.\"\n : \"To rotate, set SECRET to the new value and SECRET_PREVIOUS to the old one.\"}\n </Meta>\n )}\n </Card>\n );\n}\n\n/** Notice that `testing` is off, so a misconfigured instance in a fleet is visible (ADR 0006). */\nfunction HostingOnlyNotice(props: {\n hiddenSnapshots: number;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { hiddenSnapshots } = props;\n return (\n <div class=\"mt-1\">\n <Alert tone=\"info\" title=\"Testing is off: Storybook hosting only\">\n This instance publishes and serves Storybooks, but does not capture, compare or keep\n baselines, and no testing status is posted. Set the <code>testing</code> option to turn\n testing on.\n {hiddenSnapshots > 0\n ? ` ${hiddenSnapshots} existing snapshot(s) are hidden, not deleted, and reappear when testing is turned on.`\n : \"\"}\n </Alert>\n </div>\n );\n}\n\n/** The public origin and the capture browsers (ADR 0007), when the instance has them. */\nfunction CaptureCards(props: {\n data: AdminSystemData;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { publicOrigin, targets } = props.data;\n return (\n <>\n {publicOrigin ? (\n <div class=\"mt-1\">\n <PublicOriginCard origin={publicOrigin} />\n </div>\n ) : null}\n {targets && targets.length > 0 ? (\n <div class=\"mt-1\">\n <CaptureTargetsCard targets={targets} />\n </div>\n ) : null}\n </>\n );\n}\n\n/** Site-admin System page: adapter inventory with in-depth health. */\nexport function renderAdminSystemPage(data: AdminSystemData): RenderedContent {\n const { report, config, credentials } = data;\n return (\n <DocumentLayout title=\"System\" nav={{ active: \"admin\" }}>\n <PageHeader\n title=\"System\"\n description=\"Adapter inventory with in-depth health. Visible to site admins only.\"\n meta={\n <>\n {report.adapters.length} adapters · {report.status} · up{\" \"}\n {formatUptime(report.uptimeSecs)}\n </>\n }\n actions={\n <HStack>\n {data.aiEnabled ? (\n <Button variant=\"secondary\" size=\"sm\" href=\"/admin/ai\">\n AI usage\n </Button>\n ) : null}\n <Button variant=\"secondary\" size=\"sm\" href=\"/admin\">\n Recheck\n </Button>\n </HStack>\n }\n />\n <NotificationsPausedNotice paused={data.notificationsPaused} />\n {data.testing === false ? (\n <HostingOnlyNotice hiddenSnapshots={data.hiddenSnapshots ?? 0} />\n ) : null}\n <ServerFacts data={data} />\n <CaptureCards data={data} />\n {credentials ? (\n <div class=\"mt-1\">\n <CredentialsCard probe={credentials} config={config} />\n </div>\n ) : null}\n <div class=\"mt-1\">\n <Card>\n <SectionTitle>Adapters</SectionTitle>\n <Meta>\n Live probe per adapter (2s timeout, parallel). Setup failures surface as failed rows;\n adapters without lifecycle hooks report their wired state.\n </Meta>\n <AdapterTable report={report} />\n </Card>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoDA,SAAS,WAAW,OAA0D;CAC5E,IAAI,UAAU,MACZ,OAAO;CAET,IAAI,UAAU,YACZ,OAAO;CAET,IAAI,UAAU,YAAY,UAAU,YAClC,OAAO;CAET,OAAO;AACT;AAEA,SAAS,aAAa,YAA4B;CAChD,IAAI,aAAa,IACf,OAAO,GAAG,WAAW;CAEvB,MAAM,UAAU,KAAK,MAAM,aAAa,EAAE;CAC1C,IAAI,UAAU,IACZ,OAAO,GAAG,QAAQ,IAAI,aAAa,GAAG;CAExC,OAAO,GAAG,KAAK,MAAM,UAAU,EAAE,EAAE,IAAI,UAAU,GAAG;AACtD;AAEA,SAAS,cAAc,WAAuC;CAC5D,OAAO,cAAc,KAAA,IAAY,MAAM,GAAG,UAAU;AACtD;AAEA,SAAS,eAAe,OAAmD;CACzE,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IACrD,OAAO;CAET,OAAO,OAAO,KAAK;AACrB;;AAGA,SAAS,WAAW,OAE+B;CACjD,MAAM,EAAE,YAAY;CACpB,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,qBAAC,MAAD,EAAA,UAAA;GACE,oBAAC,UAAD,EAAA,UAAS,QAAQ,KAAa,CAAA;GAC7B,QAAQ,cAAc,oBAAC,MAAD;IAAM,IAAG;IAAO,UAAA,QAAQ;GAAkB,CAAA,IAAI;GACpE,QAAQ,eAAe,OAAO,oBAAC,MAAD;IAAM,IAAG;IAAM,UAAA;GAAwB,CAAA;EACpE,EAAA,CAAA;EACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;GAAM,IAAG;GAAQ,UAAA,QAAQ;EAAe,CAAA,EACtC,CAAA;EACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;GAAM,IAAG;GAAO,MAAA;GACb,UAAA,QAAQ;EACL,CAAA,EACJ,CAAA;EACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;GAAM,IAAG;GAAO,MAAA;GACb,UAAA,QAAQ;EACL,CAAA,EACJ,CAAA;EACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,OAAD;GAAO,MAAM,WAAW,QAAQ,KAAK;GAAG,MAAA;GACrC,UAAA,QAAQ;EACJ,CAAA,EACL,CAAA;EACJ,oBAAC,MAAD;GAAI,OAAM;GACR,UAAA,oBAAC,MAAD;IAAM,IAAG;IAAQ,UAAA,cAAc,QAAQ,SAAS;GAAQ,CAAA;EACtD,CAAA;EACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;GAAM,IAAG;GAAQ,UAAA,QAAQ,UAAU;EAAU,CAAA,EAC3C,CAAA;CACF,EAAA,GA9BK,GAAG,QAAQ,SAAS,GAAG,QAAQ,MA8BpC;AAER;;AAOA,SAAS,aAAa,OAA0E;CAC9F,MAAM,EAAE,WAAW;CACnB,IAAI,OAAO,SAAS,WAAW,GAC7B,OAAO,oBAAC,YAAD,EAAY,aAAY,qBAAsB,CAAA;CAEvD,OACE,oBAAC,OAAD,EAAA,UACE,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD,EAAA,UAAA;EACE,oBAAC,MAAD,EAAA,UAAI,UAAW,CAAA;EACf,oBAAC,MAAD,EAAA,UAAI,WAAY,CAAA;EAChB,oBAAC,MAAD,EAAA,UAAI,OAAQ,CAAA;EACZ,oBAAC,MAAD,EAAA,UAAI,UAAW,CAAA;EACf,oBAAC,MAAD,EAAA,UAAI,SAAU,CAAA;EACd,oBAAC,MAAD,EAAA,UAAI,UAAW,CAAA;EACf,oBAAC,MAAD,EAAA,UAAI,SAAU,CAAA;CACZ,EAAA,CAAA,EACC,CAAA,GACP,oBAAC,SAAD,EAAA,UACG,OAAO,SAAS,KAAK,YACpB,oBAAC,YAAD,EAAiE,QAAU,GAA1D,GAAG,QAAQ,SAAS,GAAG,QAAQ,MAA2B,CAC5E,EACI,CAAA,CACF,EAAA,CAAA,EACF,CAAA;AAEX;;AAGA,SAAS,cAAc,OAE4B;CACjD,MAAM,EAAE,MAAM,UAAU,QAAQ,UAAU,MAAM;CAChD,OACE,qBAAC,MAAD,EAAA,UAAA;EAAM;EACU;EAAK;EAAQ;EAAS;EAAsB,OAAO;EAChE,OAAO,OAAO,YAAY;EAAgB;EAAoB,MAAM;EAAM;EAAE,MAAM;EAAI;CAEnF,EAAA,CAAA;AAEV;;AAOA,SAAS,YAAY,OAAyE;CAC5F,MAAM,EAAE,QAAQ,aAAa,QAAQ,aAAa,YAAY,MAAM;CACpE,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,oBAAC,cAAD,EAAA,UAAc,SAAoB,CAAA;EAClC,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA;IACE,oBAAC,MAAD;KAAM,OAAM;KAAS,OAAO,OAAO;IAAS,CAAA;IAC5C,oBAAC,MAAD;KAAM,OAAM;KAAS,OAAO,aAAa,OAAO,UAAU;IAAI,CAAA;IAC9D,oBAAC,MAAD;KAAM,OAAM;KAAU,OAAO,OAAO;IAAU,CAAA;GAC3C;;EACL,qBAAC,MAAD,EAAA,UAAA;GAAM;GACE,cAAc,YAAY;GAAsC;GAAa;GAClF,eAAe,OAAO,YAAY;GAAE;GAAmB;GACvD,OAAO,kBAAkB,OACtB,aACA,GAAG,eAAe,OAAO,aAAa,EAAE;EACxC,EAAA,CAAA;EACL,eAAe,YAAY,SAAS,IACnC,oBAAC,QAAD,EAAA,UACG,YAAY,KAAK,WAChB,oBAAC,OAAD,EAAA,UAAqB,OAAc,GAAvB,MAAuB,CACpC,EACK,CAAA,IACN;EACH,UAAU,oBAAC,eAAD,EAAwB,QAAU,CAAA,IAAI;EACjD,qBAAC,MAAD,EAAA,UAAA;GAAM;GACS,eAAe,OAAO,UAAU;GAAE;GAAoB;GAClE,eAAe,OAAO,mBAAmB;GAAE;GAAc;GACzD,eAAe,OAAO,cAAc;GAAE;EACnC,EAAA,CAAA;CACF,EAAA,CAAA;AAEV;;AAGA,SAAS,gBAAgB,OAG0B;CACjD,MAAM,EAAE,OAAO,WAAW;CAC1B,MAAM,WAAW,QAAQ,OAAO,cAAc;CAC9C,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,oBAAC,cAAD,EAAA,UAAc,qBAAgC,CAAA;EAC9C,oBAAC,MAAD,EAAA,UAAM,6FAEA,CAAA;EACN,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA;IACE,oBAAC,MAAD;KAAM,OAAM;KAAiB,OAAO,OAAO,MAAM,OAAO;IAAI,CAAA;IAC5D,oBAAC,MAAD;KAAM,OAAM;KAAkB,OAAO,OAAO,MAAM,QAAQ;IAAI,CAAA;IAC9D,oBAAC,MAAD;KAAM,OAAM;KAAa,OAAO,OAAO,MAAM,WAAW,MAAM;IAAI,CAAA;GAC/D;;EACJ,MAAM,WAAW,SAAS,IACzB,qBAAC,OAAD;GAAO,MAAK;GAAS,OAAM;GAA3B,UAAA;IAAkE;IAClC,MAAM,WAAW;IAAO;IACrD,CAAC,GAAG,IAAI,IAAI,MAAM,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;IAAE;GAE/D;EACL,CAAA,IAAA;EACH,YAAY,MAAM,WAAW,IAC5B,qBAAC,QAAD;GACE,QAAO;GACP,QAAO;GACP,WAAQ;GACR,aAAU;GAJZ,UAAA,CAMG,UAAU,GACX,oBAAC,QAAD;IAAQ,MAAK;IAAS,SAAQ;IAAY,MAAK;IAAK,UAAA;GAE5C,CAAA,CACJ;EAEN,CAAA,IAAA,oBAAC,MAAD,EAAA,UACG,WACG,oEACA,6EACA,CAAA;CAEJ,EAAA,CAAA;AAEV;;AAGA,SAAS,kBAAkB,OAEwB;CACjD,MAAM,EAAE,oBAAoB;CAC5B,OACE,oBAAC,OAAD;EAAK,OAAM;EACT,UAAA,qBAAC,OAAD;GAAO,MAAK;GAAO,OAAM;GAAzB,UAAA;IAAkE;IAEZ,oBAAC,QAAD,EAAA,UAAM,UAAa,CAAA;IAAC;IAEvE,kBAAkB,IACf,IAAI,gBAAgB,0FACpB;GACC;;CACJ,CAAA;AAET;;AAGA,SAAS,aAAa,OAE6B;CACjD,MAAM,EAAE,cAAc,YAAY,MAAM;CACxC,OACE,qBAAA,UAAA,EAAA,UAAA,CACG,eACC,oBAAC,OAAD;EAAK,OAAM;EACT,UAAA,oBAAC,kBAAD,EAAkB,QAAQ,aAAe,CAAA;CACtC,CAAA,IACH,MACH,WAAW,QAAQ,SAAS,IAC3B,oBAAC,OAAD;EAAK,OAAM;EACT,UAAA,oBAAC,oBAAD,EAA6B,QAAU,CAAA;CACpC,CAAA,IACH,IACJ,EAAA,CAAA;AAEN;;AAGA,SAAgB,sBAAsB,MAAwC;CAC5E,MAAM,EAAE,QAAQ,QAAQ,gBAAgB;CACxC,OACE,qBAAC,gBAAD;EAAgB,OAAM;EAAS,KAAK,EAAE,QAAQ,QAAQ;EAAtD,UAAA;GACE,oBAAC,YAAD;IACE,OAAM;IACN,aAAY;IACZ,MACE,qBAAA,UAAA,EAAA,UAAA;KACG,OAAO,SAAS;KAAO;KAAa,OAAO;KAAO;KAAM;KACxD,aAAa,OAAO,UAAU;IAC/B,EAAA,CAAA;IAEJ,SACE,qBAAC,QAAD,EAAA,UAAA,CACG,KAAK,YACJ,oBAAC,QAAD;KAAQ,SAAQ;KAAY,MAAK;KAAK,MAAK;KAAY,UAAA;IAE/C,CAAA,IACN,MACJ,oBAAC,QAAD;KAAQ,SAAQ;KAAY,MAAK;KAAK,MAAK;KAAS,UAAA;IAE5C,CAAA,CACF,EAAA,CAAA;GAEX,CAAA;GACD,oBAAC,2BAAD,EAA2B,QAAQ,KAAK,oBAAsB,CAAA;GAC7D,KAAK,YAAY,QAChB,oBAAC,mBAAD,EAAmB,iBAAiB,KAAK,mBAAmB,EAAI,CAAA,IAC9D;GACJ,oBAAC,aAAD,EAAmB,KAAO,CAAA;GAC1B,oBAAC,cAAD,EAAoB,KAAO,CAAA;GAC1B,cACC,oBAAC,OAAD;IAAK,OAAM;IACT,UAAA,oBAAC,iBAAD;KAAiB,OAAO;KAAqB;IAAS,CAAA;GACnD,CAAA,IACH;GACJ,oBAAC,OAAD;IAAK,OAAM;IACT,UAAA,qBAAC,MAAD,EAAA,UAAA;KACE,oBAAC,cAAD,EAAA,UAAc,WAAsB,CAAA;KACpC,oBAAC,MAAD,EAAA,UAAM,mJAGA,CAAA;KACN,oBAAC,cAAD,EAAsB,OAAS,CAAA;IAC3B,EAAA,CAAA;GACH,CAAA;EACS;;AAEpB"}
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { getStore } from "../store.mjs";
|
|
4
|
+
import { catalogOf, getStore, testingEnabled } from "../store.mjs";
|
|
5
5
|
import { Button } from "../ui/buttons.mjs";
|
|
6
6
|
import { Badge, EmptyState, Meta, Stat, statusTone } from "../ui/feedback.mjs";
|
|
7
|
-
import { TextareaField } from "../ui/forms.mjs";
|
|
8
7
|
import { Card, PageHeader, SectionTitle } from "../ui/layout.mjs";
|
|
9
8
|
import { HStack, VStack } from "../ui/stacks.mjs";
|
|
10
9
|
import { Thumbnail } from "../ui/thumbnail.mjs";
|
|
11
10
|
import { DocumentLayout } from "../ui/document.mjs";
|
|
12
11
|
import { loadBaselineView } from "../routers/baseline-guard.mjs";
|
|
13
|
-
import {
|
|
12
|
+
import { designUrlsFor, loadDesignResolutions, providerNameFor } from "../routers/design.handlers.mjs";
|
|
13
|
+
import { loadReviewSection } from "../routers/review-section.handlers.mjs";
|
|
14
|
+
import { snapshotCard, snapshotCardBody, snapshotCardHead, snapshotCardMeta, snapshotGrid } from "../ui/styles/review.mjs";
|
|
15
|
+
import { a11ySummaryNote, a11ySummaryOf } from "./a11y-violations.mjs";
|
|
16
|
+
import { snapshotVariant } from "./build-diff-model.mjs";
|
|
14
17
|
import { StaleBaselineNotice } from "./build-diff-stale.mjs";
|
|
15
|
-
import {
|
|
18
|
+
import { LinkedUsage } from "./build-diff-usage.mjs";
|
|
19
|
+
import { TestsCard } from "./build-tests.mjs";
|
|
20
|
+
import { DesignButtons } from "./design-links.mjs";
|
|
21
|
+
import { ChangesetCard } from "./review-changeset.mjs";
|
|
22
|
+
import { CommentsCard } from "./review-comments.mjs";
|
|
23
|
+
import { VerdictCard } from "./review-verdict.mjs";
|
|
24
|
+
import { groupComponents, parseTestResults } from "@storyshelf/core/capture";
|
|
25
|
+
import { BuildModel, CaptureAttemptModel, CaptureLogModel, LabelModel, ProjectModel, SnapshotModel } from "@storyshelf/core/models";
|
|
26
|
+
import { CANCELLABLE_BUILD_STATUSES } from "@storyshelf/core/types";
|
|
16
27
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
17
28
|
import { createUrlBuilder } from "@storyshelf/core/urls";
|
|
18
29
|
//#region src/pages/build-detail.tsx
|
|
@@ -24,22 +35,29 @@ function logTone(level) {
|
|
|
24
35
|
return "neutral";
|
|
25
36
|
}
|
|
26
37
|
/** Build overview page: snapshot grid, bulk actions, and build comments. */
|
|
27
|
-
async function renderBuildDetailPage(buildId) {
|
|
38
|
+
async function renderBuildDetailPage(buildId, usageByStory = /* @__PURE__ */ new Map()) {
|
|
28
39
|
const { db } = getStore();
|
|
29
40
|
const build = await new BuildModel(db).get(buildId);
|
|
30
41
|
if (!build) return null;
|
|
31
42
|
const project = await new ProjectModel(db).get(build.projectId);
|
|
32
43
|
if (!project) return null;
|
|
33
44
|
const snapshots = await new SnapshotModel(db).listByBuild(build.id);
|
|
34
|
-
const comments = await new CommentModel(db).listByBuild(build.id);
|
|
35
45
|
const attempts = await new CaptureAttemptModel(db).listByBuild(build.id);
|
|
36
46
|
const logs = new CaptureLogModel(db);
|
|
37
47
|
const attemptLogs = /* @__PURE__ */ new Map();
|
|
38
48
|
for (const attempt of attempts) attemptLogs.set(attempt.id, await logs.listByAttempt(attempt.id));
|
|
39
49
|
const canReview = !getStore().authEnabled || Boolean(getStore().user);
|
|
40
50
|
const { drifted } = await loadBaselineView(project, build, snapshots);
|
|
51
|
+
const designs = await loadDesignResolutions(project, snapshots).catch(() => /* @__PURE__ */ new Map());
|
|
41
52
|
const staleCount = Object.keys(drifted).length;
|
|
42
53
|
const urls = createUrlBuilder("/", getStore().config.publishedBaseDomain);
|
|
54
|
+
const testing = testingEnabled();
|
|
55
|
+
const cancelled = build.status === "cancelled";
|
|
56
|
+
const published = build.status === "approved" && attempts.length === 0;
|
|
57
|
+
const [catalog, labels] = await Promise.all([catalogOf().load(project.id, build.id).catch(() => null), new LabelModel(db).listForBuild(build.id)]);
|
|
58
|
+
const review = await loadReviewSection(project, build, catalog);
|
|
59
|
+
const components = catalog ? groupComponents(catalog.entries) : [];
|
|
60
|
+
const storyCount = catalog?.entries.filter((entry) => entry.type === "story").length ?? 0;
|
|
43
61
|
const grouped = /* @__PURE__ */ new Map();
|
|
44
62
|
for (const snap of snapshots) {
|
|
45
63
|
const key = snap.status;
|
|
@@ -47,7 +65,7 @@ async function renderBuildDetailPage(buildId) {
|
|
|
47
65
|
list.push(snap);
|
|
48
66
|
grouped.set(key, list);
|
|
49
67
|
}
|
|
50
|
-
const
|
|
68
|
+
const a11y = a11ySummaryOf(snapshots);
|
|
51
69
|
const selective = build.affectedOnly && build.affectedImportPaths !== null;
|
|
52
70
|
return /* @__PURE__ */ jsxs(DocumentLayout, {
|
|
53
71
|
title: `Build ${build.gitBranch}`,
|
|
@@ -69,16 +87,24 @@ async function renderBuildDetailPage(buildId) {
|
|
|
69
87
|
" ",
|
|
70
88
|
/* @__PURE__ */ jsx(Badge, {
|
|
71
89
|
tone: statusTone(build.status),
|
|
72
|
-
children: build.status
|
|
90
|
+
children: published ? "Published" : build.status
|
|
73
91
|
}),
|
|
74
|
-
selective ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
|
|
92
|
+
testing && selective ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
|
|
75
93
|
tone: "info",
|
|
76
94
|
children: "affected capture"
|
|
77
95
|
})] }) : null,
|
|
78
|
-
!build.affectedOnly ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
|
|
96
|
+
testing && !build.affectedOnly ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
|
|
79
97
|
tone: "neutral",
|
|
80
98
|
children: "full capture"
|
|
81
|
-
})] }) : null
|
|
99
|
+
})] }) : null,
|
|
100
|
+
labels.map((label) => /* @__PURE__ */ jsxs("span", { children: [" ", /* @__PURE__ */ jsxs(Badge, {
|
|
101
|
+
tone: "neutral",
|
|
102
|
+
children: [
|
|
103
|
+
label.typeKey,
|
|
104
|
+
": ",
|
|
105
|
+
label.value
|
|
106
|
+
]
|
|
107
|
+
})] }, `${label.typeKey}:${label.value}`))
|
|
82
108
|
] }),
|
|
83
109
|
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84
110
|
build.message ?? "No message",
|
|
@@ -87,27 +113,28 @@ async function renderBuildDetailPage(buildId) {
|
|
|
87
113
|
" ",
|
|
88
114
|
"· ",
|
|
89
115
|
new Date(build.createdAt).toLocaleString(),
|
|
90
|
-
selective ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
116
|
+
testing && selective ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
91
117
|
" ",
|
|
92
118
|
"· ",
|
|
93
|
-
|
|
119
|
+
build.snapshotCount - build.inheritedCount,
|
|
94
120
|
" rendered, ",
|
|
95
|
-
inheritedCount,
|
|
96
|
-
"
|
|
121
|
+
build.inheritedCount,
|
|
122
|
+
" ",
|
|
123
|
+
"inherited"
|
|
97
124
|
] }) : null
|
|
98
125
|
] }),
|
|
99
126
|
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
100
|
-
/* @__PURE__ */ jsx(Button, {
|
|
127
|
+
testing ? /* @__PURE__ */ jsx(Button, {
|
|
101
128
|
variant: "primary",
|
|
102
129
|
href: urls.buildDiff(project.slug, build.id),
|
|
103
130
|
children: "Review diffs"
|
|
104
|
-
}),
|
|
131
|
+
}) : null,
|
|
105
132
|
/* @__PURE__ */ jsx(Button, {
|
|
106
|
-
variant: "ghost",
|
|
133
|
+
variant: testing ? "ghost" : "primary",
|
|
107
134
|
href: urls.short(build.id),
|
|
108
135
|
children: "View Storybook"
|
|
109
136
|
}),
|
|
110
|
-
/* @__PURE__ */ jsx("form", {
|
|
137
|
+
testing ? /* @__PURE__ */ jsx("form", {
|
|
111
138
|
method: "post",
|
|
112
139
|
action: `/api/v1/projects/${project.slug}/builds/${build.id}/retry`,
|
|
113
140
|
"hx-post": `/api/v1/projects/${project.slug}/builds/${build.id}/retry`,
|
|
@@ -117,10 +144,22 @@ async function renderBuildDetailPage(buildId) {
|
|
|
117
144
|
type: "submit",
|
|
118
145
|
children: "Retry capture"
|
|
119
146
|
})
|
|
120
|
-
})
|
|
147
|
+
}) : null,
|
|
148
|
+
testing && CANCELLABLE_BUILD_STATUSES.includes(build.status) ? /* @__PURE__ */ jsx("form", {
|
|
149
|
+
method: "post",
|
|
150
|
+
action: `/api/v1/projects/${project.slug}/builds/${build.id}/cancel`,
|
|
151
|
+
"hx-post": `/api/v1/projects/${project.slug}/builds/${build.id}/cancel`,
|
|
152
|
+
"hx-target": "body",
|
|
153
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
154
|
+
variant: "danger",
|
|
155
|
+
size: "sm",
|
|
156
|
+
type: "submit",
|
|
157
|
+
children: "Cancel build"
|
|
158
|
+
})
|
|
159
|
+
}) : null
|
|
121
160
|
] })
|
|
122
161
|
}),
|
|
123
|
-
staleCount > 0 ? /* @__PURE__ */ jsx(StaleBaselineNotice, {
|
|
162
|
+
testing && staleCount > 0 ? /* @__PURE__ */ jsx(StaleBaselineNotice, {
|
|
124
163
|
project,
|
|
125
164
|
build,
|
|
126
165
|
staleCount,
|
|
@@ -130,11 +169,21 @@ async function renderBuildDetailPage(buildId) {
|
|
|
130
169
|
class: "mb-1",
|
|
131
170
|
children: /* @__PURE__ */ jsxs("div", {
|
|
132
171
|
class: "grid grid--3",
|
|
133
|
-
children: [
|
|
172
|
+
children: [catalog ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Stat, {
|
|
173
|
+
label: "Components",
|
|
174
|
+
value: components.length
|
|
175
|
+
}) }), /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Stat, {
|
|
176
|
+
label: "Stories",
|
|
177
|
+
value: storyCount
|
|
178
|
+
}) })] }) : null, testing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
134
179
|
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Stat, {
|
|
135
180
|
label: "Snapshots",
|
|
136
181
|
value: snapshots.length
|
|
137
182
|
}) }),
|
|
183
|
+
/* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(Stat, {
|
|
184
|
+
label: "Inherited",
|
|
185
|
+
value: build.inheritedCount
|
|
186
|
+
}), /* @__PURE__ */ jsx(Meta, { children: "Reused from the baseline without rendering." })] }),
|
|
138
187
|
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Stat, {
|
|
139
188
|
label: "Changed / new",
|
|
140
189
|
value: build.changedCount,
|
|
@@ -144,11 +193,16 @@ async function renderBuildDetailPage(buildId) {
|
|
|
144
193
|
label: "Approved / unchanged",
|
|
145
194
|
value: build.approvedCount,
|
|
146
195
|
tone: "success"
|
|
147
|
-
}) })
|
|
148
|
-
|
|
196
|
+
}) }),
|
|
197
|
+
/* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(Stat, {
|
|
198
|
+
label: "Accessibility findings",
|
|
199
|
+
value: a11y.findings,
|
|
200
|
+
tone: a11y.findings > 0 ? "warning" : "neutral"
|
|
201
|
+
}), /* @__PURE__ */ jsx(Meta, { children: a11ySummaryNote(a11y) })] })
|
|
202
|
+
] }) : null]
|
|
149
203
|
})
|
|
150
204
|
}),
|
|
151
|
-
attempts.length > 0 ? /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(SectionTitle, { children: "Capture attempts" }), attempts.map((attempt, index) => /* @__PURE__ */ jsxs("details", {
|
|
205
|
+
testing && attempts.length > 0 ? /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(SectionTitle, { children: "Capture attempts" }), attempts.map((attempt, index) => /* @__PURE__ */ jsxs("details", {
|
|
152
206
|
open: index === attempts.length - 1,
|
|
153
207
|
children: [
|
|
154
208
|
/* @__PURE__ */ jsxs("summary", { children: [
|
|
@@ -193,7 +247,8 @@ async function renderBuildDetailPage(buildId) {
|
|
|
193
247
|
})
|
|
194
248
|
]
|
|
195
249
|
}, attempt.id))] }) : null,
|
|
196
|
-
|
|
250
|
+
/* @__PURE__ */ jsx(TestsCard, { results: parseTestResults(build.testResults) }),
|
|
251
|
+
testing && canReview && (build.status === "reviewing" || build.status === "comparing") ? /* @__PURE__ */ jsx("div", {
|
|
197
252
|
class: "mb-1",
|
|
198
253
|
children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(HStack, { children: [
|
|
199
254
|
/* @__PURE__ */ jsx(Meta, {
|
|
@@ -235,7 +290,11 @@ async function renderBuildDetailPage(buildId) {
|
|
|
235
290
|
})
|
|
236
291
|
] }) })
|
|
237
292
|
}) : null,
|
|
238
|
-
|
|
293
|
+
review ? /* @__PURE__ */ jsx("div", {
|
|
294
|
+
class: "mb-1",
|
|
295
|
+
children: /* @__PURE__ */ jsxs(VStack, { children: [testing || cancelled ? null : /* @__PURE__ */ jsx(VerdictCard, { data: review }), /* @__PURE__ */ jsx(ChangesetCard, { data: review })] })
|
|
296
|
+
}) : null,
|
|
297
|
+
!testing ? null : snapshots.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
239
298
|
title: "No snapshots yet",
|
|
240
299
|
description: "Capture is pending or failed. Try retrying capture."
|
|
241
300
|
}) : /* @__PURE__ */ jsx("div", {
|
|
@@ -256,7 +315,7 @@ async function renderBuildDetailPage(buildId) {
|
|
|
256
315
|
/* @__PURE__ */ jsxs("span", {
|
|
257
316
|
class: snapshotCardMeta,
|
|
258
317
|
children: [
|
|
259
|
-
snap
|
|
318
|
+
snapshotVariant(snap),
|
|
260
319
|
" · ",
|
|
261
320
|
snap.viewportWidth,
|
|
262
321
|
"×",
|
|
@@ -290,6 +349,10 @@ async function renderBuildDetailPage(buildId) {
|
|
|
290
349
|
snap.diffPixels === null || snap.diffPixels === void 0 ? "" : ` · ${snap.diffPixels} px`
|
|
291
350
|
]
|
|
292
351
|
}),
|
|
352
|
+
snap.storyImportPath && usageByStory.has(snap.storyImportPath) ? /* @__PURE__ */ jsx(LinkedUsage, {
|
|
353
|
+
usage: usageByStory.get(snap.storyImportPath) ?? [],
|
|
354
|
+
publishedBaseDomain: getStore().config.publishedBaseDomain
|
|
355
|
+
}) : null,
|
|
293
356
|
/* @__PURE__ */ jsxs(HStack, { children: [
|
|
294
357
|
/* @__PURE__ */ jsx(Button, {
|
|
295
358
|
variant: "secondary",
|
|
@@ -303,6 +366,16 @@ async function renderBuildDetailPage(buildId) {
|
|
|
303
366
|
href: urls.short(build.id),
|
|
304
367
|
children: "View Storybook"
|
|
305
368
|
}),
|
|
369
|
+
snap.storyImportPath ? /* @__PURE__ */ jsx(Button, {
|
|
370
|
+
variant: "ghost",
|
|
371
|
+
size: "sm",
|
|
372
|
+
href: `${urls.component(project.slug, snap.storyImportPath)}#${snap.storyId}`,
|
|
373
|
+
children: "Component"
|
|
374
|
+
}) : null,
|
|
375
|
+
/* @__PURE__ */ jsx(DesignButtons, {
|
|
376
|
+
urls: designUrlsFor(designs, snap),
|
|
377
|
+
providerName: providerNameFor
|
|
378
|
+
}),
|
|
306
379
|
canReview && (snap.status === "new" || snap.status === "changed") ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("form", {
|
|
307
380
|
method: "post",
|
|
308
381
|
action: `/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`,
|
|
@@ -334,55 +407,10 @@ async function renderBuildDetailPage(buildId) {
|
|
|
334
407
|
})]
|
|
335
408
|
}, snap.id))
|
|
336
409
|
}),
|
|
337
|
-
/* @__PURE__ */ jsx("div", {
|
|
410
|
+
review ? /* @__PURE__ */ jsx("div", {
|
|
338
411
|
class: "mt-1",
|
|
339
|
-
children: /* @__PURE__ */ jsxs(
|
|
340
|
-
|
|
341
|
-
comments.length === 0 ? /* @__PURE__ */ jsx(Meta, { children: "No comments. Add one in the diff review page for a specific snapshot." }) : null,
|
|
342
|
-
/* @__PURE__ */ jsx(VStack, { children: comments.map((comment) => /* @__PURE__ */ jsxs("div", {
|
|
343
|
-
class: reviewComment,
|
|
344
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
345
|
-
class: reviewCommentHead,
|
|
346
|
-
children: [
|
|
347
|
-
/* @__PURE__ */ jsx("strong", { children: comment.userId ?? "anonymous" }),
|
|
348
|
-
/* @__PURE__ */ jsxs("span", { children: ["· ", new Date(comment.createdAt).toLocaleString()] }),
|
|
349
|
-
comment.snapshotId ? /* @__PURE__ */ jsx(Badge, {
|
|
350
|
-
tone: "neutral",
|
|
351
|
-
children: snapshots.find((snapshot) => snapshot.id === comment.snapshotId)?.storyName ?? comment.snapshotId.slice(0, 6)
|
|
352
|
-
}) : /* @__PURE__ */ jsx(Badge, {
|
|
353
|
-
tone: "neutral",
|
|
354
|
-
children: "build"
|
|
355
|
-
}),
|
|
356
|
-
comment.resolved ? /* @__PURE__ */ jsx(Badge, {
|
|
357
|
-
tone: "success",
|
|
358
|
-
children: "resolved"
|
|
359
|
-
}) : null
|
|
360
|
-
]
|
|
361
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
362
|
-
class: reviewCommentBody,
|
|
363
|
-
children: comment.body
|
|
364
|
-
})]
|
|
365
|
-
}, comment.id)) }),
|
|
366
|
-
/* @__PURE__ */ jsx("form", {
|
|
367
|
-
method: "post",
|
|
368
|
-
action: `/api/v1/projects/${project.slug}/builds/${build.id}/comments`,
|
|
369
|
-
"hx-post": `/api/v1/projects/${project.slug}/builds/${build.id}/comments`,
|
|
370
|
-
"hx-target": "body",
|
|
371
|
-
class: "mt-1 max-w-prose",
|
|
372
|
-
children: /* @__PURE__ */ jsxs(VStack, { children: [/* @__PURE__ */ jsx(TextareaField, {
|
|
373
|
-
label: "Add build comment",
|
|
374
|
-
name: "body",
|
|
375
|
-
rows: 3,
|
|
376
|
-
required: true,
|
|
377
|
-
placeholder: "Leave a comment on this build…"
|
|
378
|
-
}), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
|
|
379
|
-
variant: "primary",
|
|
380
|
-
type: "submit",
|
|
381
|
-
children: "Comment"
|
|
382
|
-
}) })] })
|
|
383
|
-
})
|
|
384
|
-
] })
|
|
385
|
-
})
|
|
412
|
+
children: /* @__PURE__ */ jsxs(VStack, { children: [testing && !cancelled ? /* @__PURE__ */ jsx(VerdictCard, { data: review }) : null, /* @__PURE__ */ jsx(CommentsCard, { data: review })] })
|
|
413
|
+
}) : null
|
|
386
414
|
]
|
|
387
415
|
});
|
|
388
416
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-detail.mjs","names":[],"sources":["../../src/pages/build-detail.tsx"],"sourcesContent":["import { BuildModel } from \"@storyshelf/core/models\";\nimport { CaptureAttemptModel } from \"@storyshelf/core/models\";\nimport { CaptureLogModel } from \"@storyshelf/core/models\";\nimport { CommentModel } from \"@storyshelf/core/models\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { SnapshotModel } from \"@storyshelf/core/models\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { loadBaselineView } from \"../routers/baseline-guard.ts\";\nimport { getStore } from \"../store.ts\";\nimport {\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Stat,\n TextareaField,\n Thumbnail,\n VStack,\n statusTone,\n} from \"../ui/components.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport {\n reviewComment,\n reviewCommentBody,\n reviewCommentHead,\n snapshotCard,\n snapshotCardBody,\n snapshotCardHead,\n snapshotCardMeta,\n snapshotGrid,\n} from \"../ui/styles/review.ts\";\nimport { StaleBaselineNotice } from \"./build-diff-stale.tsx\";\n\n/** Map a capture log level to its badge tone. */\nfunction logTone(level: string): \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\" {\n if (level === \"error\") {\n return \"danger\";\n }\n if (level === \"warn\") {\n return \"warning\";\n }\n if (level === \"info\") {\n return \"info\";\n }\n return \"neutral\";\n}\n/** Build overview page: snapshot grid, bulk actions, and build comments. */\nexport async function renderBuildDetailPage(buildId: string): Promise<RenderedContent | null> {\n const { db } = getStore();\n const build = await new BuildModel(db).get(buildId);\n if (!build) {\n return null;\n }\n const project = await new ProjectModel(db).get(build.projectId);\n if (!project) {\n return null;\n }\n const snapshots = await new SnapshotModel(db).listByBuild(build.id);\n const comments = await new CommentModel(db).listByBuild(build.id);\n const attempts = await new CaptureAttemptModel(db).listByBuild(build.id);\n const logs = new CaptureLogModel(db);\n const attemptLogs = new Map<string, Awaited<ReturnType<typeof logs.listByAttempt>>>();\n /* oxlint-disable-next-line eslint/no-await-in-loop -- attempts are few; sequential reads keep code simple */\n for (const attempt of attempts) {\n attemptLogs.set(attempt.id, await logs.listByAttempt(attempt.id));\n }\n const canReview = !getStore().authEnabled || Boolean(getStore().user);\n const { drifted } = await loadBaselineView(project, build, snapshots);\n const staleCount = Object.keys(drifted).length;\n const urls = createUrlBuilder(\"/\", getStore().config.publishedBaseDomain);\n\n const grouped = new Map<string, typeof snapshots>();\n for (const snap of snapshots) {\n const key = snap.status;\n const list = grouped.get(key) ?? [];\n list.push(snap);\n grouped.set(key, list);\n }\n const inheritedCount = snapshots.filter((snap) => snap.inherited).length;\n const selective = build.affectedOnly && build.affectedImportPaths !== null;\n\n return (\n <DocumentLayout\n title={`Build ${build.gitBranch}`}\n nav={{\n active: \"builds\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: [{ label: `${build.gitBranch} · ${build.gitSha.slice(0, 7)}` }],\n }}\n >\n <PageHeader\n title={\n <>\n {build.gitBranch} <Meta as=\"span\">· {build.gitSha.slice(0, 7)}</Meta>{\" \"}\n <Badge tone={statusTone(build.status)}>{build.status}</Badge>\n {selective ? (\n <>\n {\" \"}\n <Badge tone=\"info\">affected capture</Badge>\n </>\n ) : null}\n {!build.affectedOnly ? (\n <>\n {\" \"}\n <Badge tone=\"neutral\">full capture</Badge>\n </>\n ) : null}\n </>\n }\n description={\n <>\n {build.message ?? \"No message\"}{\" \"}\n {build.authorName\n ? `· ${build.authorName}${build.authorEmail ? ` <${build.authorEmail}>` : \"\"}`\n : \"\"}{\" \"}\n · {new Date(build.createdAt).toLocaleString()}\n {selective ? (\n <>\n {\" \"}\n · {snapshots.length - inheritedCount} rendered, {inheritedCount} inherited\n </>\n ) : null}\n </>\n }\n actions={\n <>\n <Button variant=\"primary\" href={urls.buildDiff(project.slug, build.id)}>\n Review diffs\n </Button>\n <Button variant=\"ghost\" href={urls.short(build.id)}>\n View Storybook\n </Button>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/retry`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/retry`}\n hx-target=\"body\"\n >\n <Button variant=\"secondary\" type=\"submit\">\n Retry capture\n </Button>\n </form>\n </>\n }\n />\n\n {staleCount > 0 ? (\n <StaleBaselineNotice\n project={project}\n build={build}\n staleCount={staleCount}\n canReview={canReview}\n />\n ) : null}\n\n <div class=\"mb-1\">\n <div class=\"grid grid--3\">\n <Card>\n <Stat label=\"Snapshots\" value={snapshots.length} />\n </Card>\n <Card>\n <Stat label=\"Changed / new\" value={build.changedCount} tone=\"warning\" />\n </Card>\n <Card>\n <Stat label=\"Approved / unchanged\" value={build.approvedCount} tone=\"success\" />\n </Card>\n </div>\n </div>\n\n {attempts.length > 0 ? (\n <Card>\n <SectionTitle>Capture attempts</SectionTitle>\n {attempts.map((attempt, index): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <details key={attempt.id} open={index === attempts.length - 1}>\n <summary>\n Attempt {attempt.attemptNo}{\" \"}\n <Badge tone={statusTone(attempt.status)}>{attempt.status}</Badge>{\" \"}\n <Meta as=\"span\">\n {attempt.storyCount} stories\n {attempt.reqId ? ` · ${attempt.reqId}` : \"\"}\n </Meta>\n </summary>\n {attempt.error ? <Meta as=\"pre\">{attempt.error}</Meta> : null}\n <div class=\"mt-1\">\n <VStack>\n {(attemptLogs.get(attempt.id) ?? []).map(\n (line): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <HStack key={line.id} align=\"baseline\" wrap={false}>\n <Badge tone={logTone(line.level)}>{line.level}</Badge>\n <span>{line.message}</span>\n {line.fields ? <Meta as=\"code\">{line.fields}</Meta> : null}\n </HStack>\n ),\n )}\n </VStack>\n </div>\n </details>\n ))}\n </Card>\n ) : null}\n\n {canReview && (build.status === \"reviewing\" || build.status === \"comparing\") ? (\n <div class=\"mb-1\">\n <Card>\n <HStack>\n <Meta as=\"span\">Bulk actions:</Meta>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/approve-all`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/approve-all`}\n hx-target=\"body\"\n hx-confirm=\"Approve all remaining changes?\"\n data-toast=\"Approved all remaining changes\"\n >\n <Button variant=\"primary\" size=\"sm\" type=\"submit\">\n Approve all\n </Button>\n </form>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/reject-all`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/reject-all`}\n hx-target=\"body\"\n hx-confirm=\"Reject all remaining changes?\"\n data-toast=\"Rejected all remaining changes\"\n data-toast-tone=\"warning\"\n >\n <Button variant=\"danger\" size=\"sm\" type=\"submit\">\n Reject all\n </Button>\n </form>\n <Meta as=\"span\">Or review individually in the diff view.</Meta>\n </HStack>\n </Card>\n </div>\n ) : null}\n\n {snapshots.length === 0 ? (\n <EmptyState\n title=\"No snapshots yet\"\n description=\"Capture is pending or failed. Try retrying capture.\"\n />\n ) : (\n <div class={snapshotGrid}>\n {snapshots.map((snap): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <div key={snap.id} class={snapshotCard}>\n <div class={snapshotCardHead}>\n <Badge tone={statusTone(snap.status)}>{snap.status}</Badge>\n {snap.inherited ? (\n <>\n {\" \"}\n <Badge tone=\"neutral\">inherited</Badge>\n </>\n ) : null}\n <span class={snapshotCardMeta}>\n {snap.viewportName} · {snap.viewportWidth}×{snap.viewportHeight}\n </span>\n </div>\n <div class={snapshotCardBody}>\n <a href={`${urls.buildDiff(project.slug, build.id)}?snapshot=${snap.id}`}>\n <Thumbnail\n src={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/image`}\n alt={`${snap.storyTitle} / ${snap.storyName}`}\n />\n </a>\n <SectionTitle>\n {snap.storyTitle} <Meta as=\"span\">/ {snap.storyName}</Meta>\n </SectionTitle>\n <Meta as=\"div\">\n {snap.storyId}\n {snap.diffRatio !== null && snap.diffRatio !== undefined\n ? ` · diff ${(snap.diffRatio * 100).toFixed(2)}%`\n : \"\"}\n {snap.diffPixels === null || snap.diffPixels === undefined\n ? \"\"\n : ` · ${snap.diffPixels} px`}\n </Meta>\n <HStack>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n href={`${urls.buildDiff(project.slug, build.id)}?snapshot=${snap.id}`}\n >\n Review\n </Button>\n <Button variant=\"ghost\" size=\"sm\" href={urls.short(build.id)}>\n View Storybook\n </Button>\n {canReview && (snap.status === \"new\" || snap.status === \"changed\") ? (\n <>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`}\n hx-target=\"body\"\n data-toast={`Approved ${snap.storyName}`}\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Approve\n </Button>\n </form>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/reject`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/reject`}\n hx-target=\"body\"\n data-toast={`Rejected ${snap.storyName}`}\n data-toast-tone=\"warning\"\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Reject\n </Button>\n </form>\n </>\n ) : null}\n </HStack>\n </div>\n </div>\n ))}\n </div>\n )}\n\n <div class=\"mt-1\">\n <Card>\n <SectionTitle>Comments</SectionTitle>\n {comments.length === 0 ? (\n <Meta>No comments. Add one in the diff review page for a specific snapshot.</Meta>\n ) : null}\n <VStack>\n {comments.map((comment): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <div key={comment.id} class={reviewComment}>\n <div class={reviewCommentHead}>\n <strong>{comment.userId ?? \"anonymous\"}</strong>\n <span>· {new Date(comment.createdAt).toLocaleString()}</span>\n {comment.snapshotId ? (\n <Badge tone=\"neutral\">\n {snapshots.find((snapshot) => snapshot.id === comment.snapshotId)\n ?.storyName ?? comment.snapshotId.slice(0, 6)}\n </Badge>\n ) : (\n <Badge tone=\"neutral\">build</Badge>\n )}\n {comment.resolved ? <Badge tone=\"success\">resolved</Badge> : null}\n </div>\n <p class={reviewCommentBody}>{comment.body}</p>\n </div>\n ))}\n </VStack>\n\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/comments`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/comments`}\n hx-target=\"body\"\n class=\"mt-1 max-w-prose\"\n >\n <VStack>\n <TextareaField\n label=\"Add build comment\"\n name=\"body\"\n rows={3}\n required\n placeholder=\"Leave a comment on this build…\"\n />\n <div>\n <Button variant=\"primary\" type=\"submit\">\n Comment\n </Button>\n </div>\n </VStack>\n </form>\n </Card>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build-detail.mjs","names":[],"sources":["../../src/pages/build-detail.tsx"],"sourcesContent":["import { groupComponents, parseTestResults } from \"@storyshelf/core/capture\";\nimport { BuildModel } from \"@storyshelf/core/models\";\nimport { CaptureAttemptModel } from \"@storyshelf/core/models\";\nimport { CaptureLogModel } from \"@storyshelf/core/models\";\nimport { LabelModel } from \"@storyshelf/core/models\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { SnapshotModel } from \"@storyshelf/core/models\";\nimport { CANCELLABLE_BUILD_STATUSES } from \"@storyshelf/core/types\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { loadBaselineView } from \"../routers/baseline-guard.ts\";\nimport {\n designUrlsFor,\n loadDesignResolutions,\n providerNameFor,\n} from \"../routers/design.handlers.ts\";\nimport { loadReviewSection } from \"../routers/review-section.handlers.ts\";\nimport { catalogOf, getStore, testingEnabled } from \"../store.ts\";\nimport {\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Stat,\n Thumbnail,\n VStack,\n statusTone,\n} from \"../ui/components.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport {\n snapshotCard,\n snapshotCardBody,\n snapshotCardHead,\n snapshotCardMeta,\n snapshotGrid,\n} from \"../ui/styles/review.ts\";\nimport { a11ySummaryNote, a11ySummaryOf } from \"./a11y-violations.tsx\";\nimport { snapshotVariant } from \"./build-diff-model.ts\";\nimport { StaleBaselineNotice } from \"./build-diff-stale.tsx\";\nimport { LinkedUsage, type LinkedUsageItem } from \"./build-diff-usage.tsx\";\nimport { TestsCard } from \"./build-tests.tsx\";\nimport { DesignButtons } from \"./design-links.tsx\";\nimport { ChangesetCard } from \"./review-changeset.tsx\";\nimport { CommentsCard } from \"./review-comments.tsx\";\nimport { VerdictCard } from \"./review-verdict.tsx\";\n\n/** Map a capture log level to its badge tone. */\nfunction logTone(level: string): \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\" {\n if (level === \"error\") {\n return \"danger\";\n }\n if (level === \"warn\") {\n return \"warning\";\n }\n if (level === \"info\") {\n return \"info\";\n }\n return \"neutral\";\n}\n/** Build overview page: snapshot grid, bulk actions, and build comments. */\nexport async function renderBuildDetailPage(\n buildId: string,\n usageByStory: Map<string, LinkedUsageItem[]> = new Map(),\n): Promise<RenderedContent | null> {\n const { db } = getStore();\n const build = await new BuildModel(db).get(buildId);\n if (!build) {\n return null;\n }\n const project = await new ProjectModel(db).get(build.projectId);\n if (!project) {\n return null;\n }\n const snapshots = await new SnapshotModel(db).listByBuild(build.id);\n const attempts = await new CaptureAttemptModel(db).listByBuild(build.id);\n const logs = new CaptureLogModel(db);\n const attemptLogs = new Map<string, Awaited<ReturnType<typeof logs.listByAttempt>>>();\n /* oxlint-disable-next-line eslint/no-await-in-loop -- attempts are few; sequential reads keep code simple */\n for (const attempt of attempts) {\n attemptLogs.set(attempt.id, await logs.listByAttempt(attempt.id));\n }\n const canReview = !getStore().authEnabled || Boolean(getStore().user);\n const { drifted } = await loadBaselineView(project, build, snapshots);\n const designs = await loadDesignResolutions(project, snapshots).catch(() => new Map());\n const staleCount = Object.keys(drifted).length;\n const urls = createUrlBuilder(\"/\", getStore().config.publishedBaseDomain);\n const testing = testingEnabled();\n const cancelled = build.status === \"cancelled\";\n const published = build.status === \"approved\" && attempts.length === 0;\n const [catalog, labels] = await Promise.all([\n catalogOf()\n .load(project.id, build.id)\n .catch(() => null),\n new LabelModel(db).listForBuild(build.id),\n ]);\n const review = await loadReviewSection(project, build, catalog);\n const components = catalog ? groupComponents(catalog.entries) : [];\n const storyCount = catalog?.entries.filter((entry) => entry.type === \"story\").length ?? 0;\n\n const grouped = new Map<string, typeof snapshots>();\n for (const snap of snapshots) {\n const key = snap.status;\n const list = grouped.get(key) ?? [];\n list.push(snap);\n grouped.set(key, list);\n }\n const a11y = a11ySummaryOf(snapshots);\n const selective = build.affectedOnly && build.affectedImportPaths !== null;\n\n return (\n <DocumentLayout\n title={`Build ${build.gitBranch}`}\n nav={{\n active: \"builds\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: [{ label: `${build.gitBranch} · ${build.gitSha.slice(0, 7)}` }],\n }}\n >\n <PageHeader\n title={\n <>\n {build.gitBranch} <Meta as=\"span\">· {build.gitSha.slice(0, 7)}</Meta>{\" \"}\n <Badge tone={statusTone(build.status)}>{published ? \"Published\" : build.status}</Badge>\n {testing && selective ? (\n <>\n {\" \"}\n <Badge tone=\"info\">affected capture</Badge>\n </>\n ) : null}\n {testing && !build.affectedOnly ? (\n <>\n {\" \"}\n <Badge tone=\"neutral\">full capture</Badge>\n </>\n ) : null}\n {labels.map((label): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <span key={`${label.typeKey}:${label.value}`}>\n {\" \"}\n <Badge tone=\"neutral\">\n {label.typeKey}: {label.value}\n </Badge>\n </span>\n ))}\n </>\n }\n description={\n <>\n {build.message ?? \"No message\"}{\" \"}\n {build.authorName\n ? `· ${build.authorName}${build.authorEmail ? ` <${build.authorEmail}>` : \"\"}`\n : \"\"}{\" \"}\n · {new Date(build.createdAt).toLocaleString()}\n {testing && selective ? (\n <>\n {\" \"}\n · {build.snapshotCount - build.inheritedCount} rendered, {build.inheritedCount}{\" \"}\n inherited\n </>\n ) : null}\n </>\n }\n actions={\n <>\n {testing ? (\n <Button variant=\"primary\" href={urls.buildDiff(project.slug, build.id)}>\n Review diffs\n </Button>\n ) : null}\n <Button variant={testing ? \"ghost\" : \"primary\"} href={urls.short(build.id)}>\n View Storybook\n </Button>\n {testing ? (\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/retry`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/retry`}\n hx-target=\"body\"\n >\n <Button variant=\"secondary\" type=\"submit\">\n Retry capture\n </Button>\n </form>\n ) : null}\n {testing && CANCELLABLE_BUILD_STATUSES.includes(build.status) ? (\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/cancel`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/cancel`}\n hx-target=\"body\"\n >\n <Button variant=\"danger\" size=\"sm\" type=\"submit\">\n Cancel build\n </Button>\n </form>\n ) : null}\n </>\n }\n />\n\n {testing && staleCount > 0 ? (\n <StaleBaselineNotice\n project={project}\n build={build}\n staleCount={staleCount}\n canReview={canReview}\n />\n ) : null}\n\n <div class=\"mb-1\">\n <div class=\"grid grid--3\">\n {catalog ? (\n <>\n <Card>\n <Stat label=\"Components\" value={components.length} />\n </Card>\n <Card>\n <Stat label=\"Stories\" value={storyCount} />\n </Card>\n </>\n ) : null}\n {testing ? (\n <>\n <Card>\n <Stat label=\"Snapshots\" value={snapshots.length} />\n </Card>\n <Card>\n <Stat label=\"Inherited\" value={build.inheritedCount} />\n <Meta>Reused from the baseline without rendering.</Meta>\n </Card>\n <Card>\n <Stat label=\"Changed / new\" value={build.changedCount} tone=\"warning\" />\n </Card>\n <Card>\n <Stat label=\"Approved / unchanged\" value={build.approvedCount} tone=\"success\" />\n </Card>\n <Card>\n <Stat\n label=\"Accessibility findings\"\n value={a11y.findings}\n tone={a11y.findings > 0 ? \"warning\" : \"neutral\"}\n />\n <Meta>{a11ySummaryNote(a11y)}</Meta>\n </Card>\n </>\n ) : null}\n </div>\n </div>\n\n {testing && attempts.length > 0 ? (\n <Card>\n <SectionTitle>Capture attempts</SectionTitle>\n {attempts.map((attempt, index): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <details key={attempt.id} open={index === attempts.length - 1}>\n <summary>\n Attempt {attempt.attemptNo}{\" \"}\n <Badge tone={statusTone(attempt.status)}>{attempt.status}</Badge>{\" \"}\n <Meta as=\"span\">\n {attempt.storyCount} stories\n {attempt.reqId ? ` · ${attempt.reqId}` : \"\"}\n </Meta>\n </summary>\n {attempt.error ? <Meta as=\"pre\">{attempt.error}</Meta> : null}\n <div class=\"mt-1\">\n <VStack>\n {(attemptLogs.get(attempt.id) ?? []).map(\n (line): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <HStack key={line.id} align=\"baseline\" wrap={false}>\n <Badge tone={logTone(line.level)}>{line.level}</Badge>\n <span>{line.message}</span>\n {line.fields ? <Meta as=\"code\">{line.fields}</Meta> : null}\n </HStack>\n ),\n )}\n </VStack>\n </div>\n </details>\n ))}\n </Card>\n ) : null}\n\n <TestsCard results={parseTestResults(build.testResults)} />\n\n {testing && canReview && (build.status === \"reviewing\" || build.status === \"comparing\") ? (\n <div class=\"mb-1\">\n <Card>\n <HStack>\n <Meta as=\"span\">Bulk actions:</Meta>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/approve-all`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/approve-all`}\n hx-target=\"body\"\n hx-confirm=\"Approve all remaining changes?\"\n data-toast=\"Approved all remaining changes\"\n >\n <Button variant=\"primary\" size=\"sm\" type=\"submit\">\n Approve all\n </Button>\n </form>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/reject-all`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/reject-all`}\n hx-target=\"body\"\n hx-confirm=\"Reject all remaining changes?\"\n data-toast=\"Rejected all remaining changes\"\n data-toast-tone=\"warning\"\n >\n <Button variant=\"danger\" size=\"sm\" type=\"submit\">\n Reject all\n </Button>\n </form>\n <Meta as=\"span\">Or review individually in the diff view.</Meta>\n </HStack>\n </Card>\n </div>\n ) : null}\n\n {review ? (\n <div class=\"mb-1\">\n <VStack>\n {testing || cancelled ? null : <VerdictCard data={review} />}\n <ChangesetCard data={review} />\n </VStack>\n </div>\n ) : null}\n\n {!testing ? null : snapshots.length === 0 ? (\n <EmptyState\n title=\"No snapshots yet\"\n description=\"Capture is pending or failed. Try retrying capture.\"\n />\n ) : (\n <div class={snapshotGrid}>\n {snapshots.map((snap): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <div key={snap.id} class={snapshotCard}>\n <div class={snapshotCardHead}>\n <Badge tone={statusTone(snap.status)}>{snap.status}</Badge>\n {snap.inherited ? (\n <>\n {\" \"}\n <Badge tone=\"neutral\">inherited</Badge>\n </>\n ) : null}\n <span class={snapshotCardMeta}>\n {snapshotVariant(snap)} · {snap.viewportWidth}×{snap.viewportHeight}\n </span>\n </div>\n <div class={snapshotCardBody}>\n <a href={`${urls.buildDiff(project.slug, build.id)}?snapshot=${snap.id}`}>\n <Thumbnail\n src={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/image`}\n alt={`${snap.storyTitle} / ${snap.storyName}`}\n />\n </a>\n <SectionTitle>\n {snap.storyTitle} <Meta as=\"span\">/ {snap.storyName}</Meta>\n </SectionTitle>\n <Meta as=\"div\">\n {snap.storyId}\n {snap.diffRatio !== null && snap.diffRatio !== undefined\n ? ` · diff ${(snap.diffRatio * 100).toFixed(2)}%`\n : \"\"}\n {snap.diffPixels === null || snap.diffPixels === undefined\n ? \"\"\n : ` · ${snap.diffPixels} px`}\n </Meta>\n {snap.storyImportPath && usageByStory.has(snap.storyImportPath) ? (\n <LinkedUsage\n usage={usageByStory.get(snap.storyImportPath) ?? []}\n publishedBaseDomain={getStore().config.publishedBaseDomain}\n />\n ) : null}\n <HStack>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n href={`${urls.buildDiff(project.slug, build.id)}?snapshot=${snap.id}`}\n >\n Review\n </Button>\n <Button variant=\"ghost\" size=\"sm\" href={urls.short(build.id)}>\n View Storybook\n </Button>\n {snap.storyImportPath ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n href={`${urls.component(project.slug, snap.storyImportPath)}#${snap.storyId}`}\n >\n Component\n </Button>\n ) : null}\n <DesignButtons\n urls={designUrlsFor(designs, snap)}\n providerName={providerNameFor}\n />\n {canReview && (snap.status === \"new\" || snap.status === \"changed\") ? (\n <>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`}\n hx-target=\"body\"\n data-toast={`Approved ${snap.storyName}`}\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Approve\n </Button>\n </form>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/reject`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/reject`}\n hx-target=\"body\"\n data-toast={`Rejected ${snap.storyName}`}\n data-toast-tone=\"warning\"\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Reject\n </Button>\n </form>\n </>\n ) : null}\n </HStack>\n </div>\n </div>\n ))}\n </div>\n )}\n\n {review ? (\n <div class=\"mt-1\">\n <VStack>\n {testing && !cancelled ? <VerdictCard data={review} /> : null}\n <CommentsCard data={review} />\n </VStack>\n </div>\n ) : null}\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
|