@storyshelf/app 0.6.2 → 0.8.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 +2 -6
- package/dist/asset-manifest.mjs +14 -7
- package/dist/asset-manifest.mjs.map +1 -1
- package/dist/assets/{htmx.min.js → htmx.mjs} +9 -1
- package/dist/assets/htmx.mjs.map +1 -0
- package/dist/assets/mark.mjs +9 -0
- package/dist/assets/mark.mjs.map +1 -0
- package/dist/boot-tasks.mjs +79 -0
- package/dist/boot-tasks.mjs.map +1 -0
- package/dist/build-state-flags.mjs +17 -0
- package/dist/build-state-flags.mjs.map +1 -0
- package/dist/build-state-setup.mjs +74 -0
- package/dist/build-state-setup.mjs.map +1 -0
- package/dist/capture-alerts.mjs +45 -0
- package/dist/capture-alerts.mjs.map +1 -0
- package/dist/capture-process.mjs +58 -0
- package/dist/capture-process.mjs.map +1 -0
- package/dist/capture-setup.mjs +96 -119
- package/dist/capture-setup.mjs.map +1 -1
- package/dist/connections.mjs +33 -0
- package/dist/connections.mjs.map +1 -0
- package/dist/consumer-setup.mjs +57 -0
- package/dist/consumer-setup.mjs.map +1 -0
- package/dist/credential-keys.mjs +1 -2
- package/dist/credentials.mjs +1 -2
- package/dist/git-status.mjs +45 -0
- package/dist/git-status.mjs.map +1 -0
- package/dist/hidden-data.mjs +23 -0
- package/dist/hidden-data.mjs.map +1 -0
- package/dist/index.d.mts +3 -5
- package/dist/index.mjs +204 -127
- package/dist/index.mjs.map +1 -1
- package/dist/insights/budget.mjs +48 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/deps.mjs +20 -0
- package/dist/insights/deps.mjs.map +1 -0
- package/dist/insights/errors.mjs +21 -0
- package/dist/insights/errors.mjs.map +1 -0
- package/dist/insights/health-panel.mjs +39 -0
- package/dist/insights/health-panel.mjs.map +1 -0
- package/dist/insights/job.mjs +102 -0
- package/dist/insights/job.mjs.map +1 -0
- package/dist/insights/load-health.mjs +58 -0
- package/dist/insights/load-health.mjs.map +1 -0
- package/dist/insights/load-triage.mjs +97 -0
- package/dist/insights/load-triage.mjs.map +1 -0
- package/dist/insights/panel.mjs +27 -0
- package/dist/insights/panel.mjs.map +1 -0
- package/dist/insights/request.mjs +112 -0
- package/dist/insights/request.mjs.map +1 -0
- package/dist/insights/stale.mjs +20 -0
- package/dist/insights/stale.mjs.map +1 -0
- package/dist/insights/usage-report.mjs +59 -0
- package/dist/insights/usage-report.mjs.map +1 -0
- package/dist/insights/view.mjs +47 -0
- package/dist/insights/view.mjs.map +1 -0
- package/dist/job-canceller.mjs +13 -0
- package/dist/job-canceller.mjs.map +1 -0
- package/dist/job-queue.mjs +50 -0
- package/dist/job-queue.mjs.map +1 -0
- package/dist/jobs-setup.mjs +131 -0
- package/dist/jobs-setup.mjs.map +1 -0
- package/dist/lifecycle.mjs +93 -22
- package/dist/lifecycle.mjs.map +1 -1
- package/dist/middleware/auth-gate.mjs +5 -4
- package/dist/middleware/auth-gate.mjs.map +1 -1
- package/dist/middleware/csrf.mjs +1 -2
- package/dist/middleware/rate-limit.mjs +42 -35
- package/dist/middleware/rate-limit.mjs.map +1 -1
- package/dist/middleware/request-log.mjs +4 -4
- package/dist/middleware/request-log.mjs.map +1 -1
- package/dist/middleware/require-review.mjs +50 -0
- package/dist/middleware/require-review.mjs.map +1 -0
- package/dist/middleware/require-testing.mjs +40 -0
- package/dist/middleware/require-testing.mjs.map +1 -0
- package/dist/middleware/server-timing.mjs +1 -2
- package/dist/middleware/setup-gate.mjs +1 -2
- package/dist/middleware/setup-gate.mjs.map +1 -1
- package/dist/middleware/store-scope.mjs +13 -4
- package/dist/middleware/store-scope.mjs.map +1 -1
- package/dist/notifications-pause.mjs +32 -0
- package/dist/notifications-pause.mjs.map +1 -0
- package/dist/notify.d.mts +3 -4
- package/dist/notify.mjs +79 -41
- package/dist/notify.mjs.map +1 -1
- package/dist/pages/a11y-violations.mjs +115 -0
- package/dist/pages/a11y-violations.mjs.map +1 -0
- package/dist/pages/admin-ai.mjs +77 -0
- package/dist/pages/admin-ai.mjs.map +1 -0
- package/dist/pages/admin-system-origin.mjs +132 -0
- package/dist/pages/admin-system-origin.mjs.map +1 -0
- package/dist/pages/admin-system-targets.mjs +52 -0
- package/dist/pages/admin-system-targets.mjs.map +1 -0
- package/dist/pages/admin-system.mjs +64 -15
- package/dist/pages/admin-system.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +105 -78
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff-actions.mjs +88 -35
- package/dist/pages/build-diff-actions.mjs.map +1 -1
- package/dist/pages/build-diff-comments.mjs +17 -16
- package/dist/pages/build-diff-comments.mjs.map +1 -1
- package/dist/pages/build-diff-default.mjs +53 -0
- package/dist/pages/build-diff-default.mjs.map +1 -0
- package/dist/pages/build-diff-environment.mjs +81 -0
- package/dist/pages/build-diff-environment.mjs.map +1 -0
- package/dist/pages/build-diff-header.mjs +23 -6
- package/dist/pages/build-diff-header.mjs.map +1 -1
- package/dist/pages/build-diff-insight.mjs +112 -0
- package/dist/pages/build-diff-insight.mjs.map +1 -0
- package/dist/pages/build-diff-model.mjs +74 -12
- package/dist/pages/build-diff-model.mjs.map +1 -1
- package/dist/pages/build-diff-nav.mjs +29 -14
- package/dist/pages/build-diff-nav.mjs.map +1 -1
- package/dist/pages/build-diff-reference.mjs +84 -0
- package/dist/pages/build-diff-reference.mjs.map +1 -0
- package/dist/pages/build-diff-shortcuts.mjs +6 -3
- package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
- package/dist/pages/build-diff-stale.mjs +1 -2
- package/dist/pages/build-diff-usage.mjs +62 -0
- package/dist/pages/build-diff-usage.mjs.map +1 -0
- package/dist/pages/build-diff-viewer.mjs +97 -14
- package/dist/pages/build-diff-viewer.mjs.map +1 -1
- package/dist/pages/build-diff.mjs +37 -22
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/build-tests.mjs +73 -0
- package/dist/pages/build-tests.mjs.map +1 -0
- package/dist/pages/component-detail.mjs +248 -0
- package/dist/pages/component-detail.mjs.map +1 -0
- package/dist/pages/component-stories.mjs +167 -0
- package/dist/pages/component-stories.mjs.map +1 -0
- package/dist/pages/compute-jobs.mjs +14 -28
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/design-links.mjs +181 -0
- package/dist/pages/design-links.mjs.map +1 -0
- package/dist/pages/environment-change.mjs +52 -0
- package/dist/pages/environment-change.mjs.map +1 -0
- package/dist/pages/health-panel.mjs +125 -0
- package/dist/pages/health-panel.mjs.map +1 -0
- package/dist/pages/invite.mjs +1 -2
- package/dist/pages/label-detail.mjs +1 -2
- package/dist/pages/library-grid.mjs +104 -0
- package/dist/pages/library-grid.mjs.map +1 -0
- package/dist/pages/library-model.mjs +46 -0
- package/dist/pages/library-model.mjs.map +1 -0
- package/dist/pages/library-snapshots.mjs +129 -0
- package/dist/pages/library-snapshots.mjs.map +1 -0
- package/dist/pages/library.mjs +113 -220
- package/dist/pages/library.mjs.map +1 -1
- package/dist/pages/login.mjs +1 -2
- package/dist/pages/notifications-paused.mjs +21 -0
- package/dist/pages/notifications-paused.mjs.map +1 -0
- package/dist/pages/passkey-ceremony.mjs +1 -2
- package/dist/pages/profile.mjs +54 -50
- package/dist/pages/profile.mjs.map +1 -1
- package/dist/pages/project-builds-table.mjs +3 -4
- package/dist/pages/project-builds-table.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +52 -23
- package/dist/pages/project-builds.mjs.map +1 -1
- package/dist/pages/project-create.mjs +1 -2
- package/dist/pages/project-settings.mjs +52 -14
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects-card.mjs +15 -3
- package/dist/pages/projects-card.mjs.map +1 -1
- package/dist/pages/projects.mjs +7 -4
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/review-changeset.mjs +115 -0
- package/dist/pages/review-changeset.mjs.map +1 -0
- package/dist/pages/review-comments.mjs +102 -0
- package/dist/pages/review-comments.mjs.map +1 -0
- package/dist/pages/review-verdict.mjs +101 -0
- package/dist/pages/review-verdict.mjs.map +1 -0
- package/dist/pages/root.mjs +1 -2
- package/dist/pages/settings-ai.mjs +68 -0
- package/dist/pages/settings-ai.mjs.map +1 -0
- package/dist/pages/settings-capture.mjs +123 -0
- package/dist/pages/settings-capture.mjs.map +1 -0
- package/dist/pages/settings-design-credential.mjs +98 -0
- package/dist/pages/settings-design-credential.mjs.map +1 -0
- package/dist/pages/settings-design-links.mjs +178 -0
- package/dist/pages/settings-design-links.mjs.map +1 -0
- package/dist/pages/settings-design.mjs +47 -0
- package/dist/pages/settings-design.mjs.map +1 -0
- package/dist/pages/settings-general.mjs +47 -23
- package/dist/pages/settings-general.mjs.map +1 -1
- package/dist/pages/settings-labels.mjs +44 -4
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-links.mjs +111 -0
- package/dist/pages/settings-links.mjs.map +1 -0
- package/dist/pages/settings-members.mjs +1 -2
- package/dist/pages/settings-notifications-git.mjs +127 -0
- package/dist/pages/settings-notifications-git.mjs.map +1 -0
- package/dist/pages/settings-notifications.mjs +80 -44
- package/dist/pages/settings-notifications.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +34 -4
- package/dist/pages/settings-tests.mjs.map +1 -1
- package/dist/pages/settings-tokens.mjs +1 -2
- package/dist/pages/settings-webhooks.mjs +1 -2
- package/dist/pages/storybook.mjs +1 -2
- package/dist/pages/target-choices.mjs +38 -0
- package/dist/pages/target-choices.mjs.map +1 -0
- package/dist/publish-setup.mjs +88 -0
- package/dist/publish-setup.mjs.map +1 -0
- package/dist/retention-timer.mjs +12 -12
- package/dist/retention-timer.mjs.map +1 -1
- package/dist/review-events.mjs +47 -0
- package/dist/review-events.mjs.map +1 -0
- package/dist/routers/admin-pages.mjs +46 -5
- package/dist/routers/admin-pages.mjs.map +1 -1
- package/dist/routers/admin-public-origin.mjs +92 -0
- package/dist/routers/admin-public-origin.mjs.map +1 -0
- package/dist/routers/admin.mjs +8 -4
- package/dist/routers/admin.mjs.map +1 -1
- package/dist/routers/assets.mjs +10 -4
- package/dist/routers/assets.mjs.map +1 -1
- package/dist/routers/attempts.mjs +1 -2
- package/dist/routers/auth-sync.mjs +1 -2
- package/dist/routers/auth.mjs +2 -3
- package/dist/routers/baseline-guard.mjs +4 -5
- package/dist/routers/baseline-guard.mjs.map +1 -1
- package/dist/routers/baseline-reference.mjs +72 -0
- package/dist/routers/baseline-reference.mjs.map +1 -0
- package/dist/routers/baselines.handlers.mjs +63 -0
- package/dist/routers/baselines.handlers.mjs.map +1 -0
- package/dist/routers/baselines.mjs +124 -0
- package/dist/routers/baselines.mjs.map +1 -0
- package/dist/routers/build-cancel.mjs +67 -0
- package/dist/routers/build-cancel.mjs.map +1 -0
- package/dist/routers/build-changeset.mjs +69 -0
- package/dist/routers/build-changeset.mjs.map +1 -0
- package/dist/routers/build-reviews.handlers.mjs +85 -0
- package/dist/routers/build-reviews.handlers.mjs.map +1 -0
- package/dist/routers/build-reviews.mjs +73 -0
- package/dist/routers/build-reviews.mjs.map +1 -0
- package/dist/routers/builds.handlers.mjs +65 -60
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +77 -19
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/capture-cache.mjs +29 -0
- package/dist/routers/capture-cache.mjs.map +1 -0
- package/dist/routers/capture-config.mjs +34 -0
- package/dist/routers/capture-config.mjs.map +1 -0
- package/dist/routers/comments.mjs +42 -17
- package/dist/routers/comments.mjs.map +1 -1
- package/dist/routers/comparison-image.mjs +29 -0
- package/dist/routers/comparison-image.mjs.map +1 -0
- package/dist/routers/component-catalog.handlers.mjs +70 -0
- package/dist/routers/component-catalog.handlers.mjs.map +1 -0
- package/dist/routers/component.handlers.mjs +120 -0
- package/dist/routers/component.handlers.mjs.map +1 -0
- package/dist/routers/connections.mjs +117 -0
- package/dist/routers/connections.mjs.map +1 -0
- package/dist/routers/design-connect.handlers.mjs +43 -0
- package/dist/routers/design-connect.handlers.mjs.map +1 -0
- package/dist/routers/design.handlers.mjs +113 -0
- package/dist/routers/design.handlers.mjs.map +1 -0
- package/dist/routers/flash.mjs +1 -2
- package/dist/routers/health-report.mjs +3 -5
- package/dist/routers/health-report.mjs.map +1 -1
- package/dist/routers/health.mjs +5 -5
- package/dist/routers/health.mjs.map +1 -1
- package/dist/routers/helpers.mjs +20 -5
- package/dist/routers/helpers.mjs.map +1 -1
- package/dist/routers/htmx.mjs +1 -2
- package/dist/routers/insights-ui.mjs +63 -0
- package/dist/routers/insights-ui.mjs.map +1 -0
- package/dist/routers/insights.handlers.mjs +44 -0
- package/dist/routers/insights.handlers.mjs.map +1 -0
- package/dist/routers/insights.mjs +188 -0
- package/dist/routers/insights.mjs.map +1 -0
- package/dist/routers/labels.mjs +1 -2
- package/dist/routers/links.handlers.mjs +79 -0
- package/dist/routers/links.handlers.mjs.map +1 -0
- package/dist/routers/links.mjs +138 -0
- package/dist/routers/links.mjs.map +1 -0
- package/dist/routers/media.mjs +30 -7
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs +3 -3
- package/dist/routers/members.mjs.map +1 -1
- package/dist/routers/notification-channels.mjs +18 -25
- package/dist/routers/notification-channels.mjs.map +1 -1
- package/dist/routers/notification-subscriptions.mjs +1 -2
- package/dist/routers/oauth-state.mjs +44 -0
- package/dist/routers/oauth-state.mjs.map +1 -0
- package/dist/routers/preview-base.mjs +37 -0
- package/dist/routers/preview-base.mjs.map +1 -0
- package/dist/routers/preview-token.mjs +50 -0
- package/dist/routers/preview-token.mjs.map +1 -0
- package/dist/routers/profile.mjs +4 -3
- package/dist/routers/profile.mjs.map +1 -1
- package/dist/routers/projects.mjs +18 -3
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/publish-upload.mjs +85 -0
- package/dist/routers/publish-upload.mjs.map +1 -0
- package/dist/routers/published-access.mjs +51 -0
- package/dist/routers/published-access.mjs.map +1 -0
- package/dist/routers/published-compression.mjs +109 -0
- package/dist/routers/published-compression.mjs.map +1 -0
- package/dist/routers/published-files.mjs +69 -0
- package/dist/routers/published-files.mjs.map +1 -0
- package/dist/routers/review-forms.mjs +71 -0
- package/dist/routers/review-forms.mjs.map +1 -0
- package/dist/routers/review-next.mjs +1 -2
- package/dist/routers/review-section.handlers.mjs +84 -0
- package/dist/routers/review-section.handlers.mjs.map +1 -0
- package/dist/routers/review-switch-off.mjs +28 -0
- package/dist/routers/review-switch-off.mjs.map +1 -0
- package/dist/routers/schemas.mjs +141 -24
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/scoped-models.mjs +21 -0
- package/dist/routers/scoped-models.mjs.map +1 -0
- package/dist/routers/settings-ai.mjs +34 -0
- package/dist/routers/settings-ai.mjs.map +1 -0
- package/dist/routers/settings-capture.mjs +76 -0
- package/dist/routers/settings-capture.mjs.map +1 -0
- package/dist/routers/settings-design.mjs +102 -0
- package/dist/routers/settings-design.mjs.map +1 -0
- package/dist/routers/settings-general.mjs +56 -12
- package/dist/routers/settings-general.mjs.map +1 -1
- package/dist/routers/settings-git-channel.mjs +76 -0
- package/dist/routers/settings-git-channel.mjs.map +1 -0
- package/dist/routers/settings-labels.mjs +28 -3
- package/dist/routers/settings-labels.mjs.map +1 -1
- package/dist/routers/settings-links.mjs +62 -0
- package/dist/routers/settings-links.mjs.map +1 -0
- package/dist/routers/settings-members.mjs +5 -9
- package/dist/routers/settings-members.mjs.map +1 -1
- package/dist/routers/settings-notifications.mjs +16 -12
- package/dist/routers/settings-notifications.mjs.map +1 -1
- package/dist/routers/settings-tokens.mjs +3 -7
- package/dist/routers/settings-tokens.mjs.map +1 -1
- package/dist/routers/settings-webhooks.mjs +8 -26
- package/dist/routers/settings-webhooks.mjs.map +1 -1
- package/dist/routers/settings.handlers.mjs +108 -28
- package/dist/routers/settings.handlers.mjs.map +1 -1
- package/dist/routers/settings.mjs +17 -6
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/signed-storybook.mjs +55 -0
- package/dist/routers/signed-storybook.mjs.map +1 -0
- package/dist/routers/snapshot-groups.mjs +62 -0
- package/dist/routers/snapshot-groups.mjs.map +1 -0
- package/dist/routers/snapshots.mjs +104 -7
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/storybook.mjs +57 -104
- package/dist/routers/storybook.mjs.map +1 -1
- package/dist/routers/tokens.mjs +3 -7
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +38 -7
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +6 -13
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/routers/well-known.mjs +12 -6
- package/dist/routers/well-known.mjs.map +1 -1
- package/dist/routes/api-routes.mjs +92 -0
- package/dist/routes/api-routes.mjs.map +1 -0
- package/dist/routes/classification.mjs +37 -0
- package/dist/routes/classification.mjs.map +1 -0
- package/dist/routes/openapi-classes.mjs +21 -0
- package/dist/routes/openapi-classes.mjs.map +1 -0
- package/dist/routes/page-routes.mjs +103 -0
- package/dist/routes/page-routes.mjs.map +1 -0
- package/dist/runtime.mjs +19 -15
- package/dist/runtime.mjs.map +1 -1
- package/dist/serverless.mjs +4 -0
- package/dist/store.mjs +20 -3
- package/dist/store.mjs.map +1 -1
- package/dist/targets.mjs +45 -0
- package/dist/targets.mjs.map +1 -0
- package/dist/ui/avatar.mjs +1 -2
- package/dist/ui/buttons.mjs +17 -5
- package/dist/ui/buttons.mjs.map +1 -1
- package/dist/ui/code-block.mjs +1 -2
- package/dist/ui/compare-stage.mjs +86 -54
- package/dist/ui/compare-stage.mjs.map +1 -1
- package/dist/ui/csrf-field.mjs +1 -2
- package/dist/ui/css.mjs +1 -2
- package/dist/ui/design-reference.mjs +96 -0
- package/dist/ui/design-reference.mjs.map +1 -0
- package/dist/ui/document.mjs +2 -4
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/dropdown.mjs +1 -2
- package/dist/ui/feedback.mjs +1 -2
- package/dist/ui/filter-input.mjs +1 -2
- package/dist/ui/forms.mjs +7 -7
- package/dist/ui/forms.mjs.map +1 -1
- package/dist/ui/icons/icon.mjs +1 -2
- package/dist/ui/icons/paths.mjs +2 -2
- package/dist/ui/icons/paths.mjs.map +1 -1
- package/dist/ui/icons/sprite.mjs +1 -2
- package/dist/ui/kbd.mjs +1 -2
- package/dist/ui/layout.mjs +1 -2
- package/dist/ui/preview-embed.mjs +81 -0
- package/dist/ui/preview-embed.mjs.map +1 -0
- package/dist/ui/progress.mjs +1 -2
- package/dist/ui/segmented.mjs +1 -2
- package/dist/ui/shell/account.mjs +1 -2
- package/dist/ui/shell/bare.mjs +1 -2
- package/dist/ui/shell/boot-script.mjs +1 -2
- package/dist/ui/shell/client-script.mjs +86 -2
- package/dist/ui/shell/client-script.mjs.map +1 -1
- package/dist/ui/shell/nav.mjs +13 -10
- package/dist/ui/shell/nav.mjs.map +1 -1
- package/dist/ui/shell/review-script.mjs +25 -3
- package/dist/ui/shell/review-script.mjs.map +1 -1
- package/dist/ui/shell/sidebar.mjs +3 -4
- package/dist/ui/shell/sidebar.mjs.map +1 -1
- package/dist/ui/shell/topbar.mjs +1 -2
- package/dist/ui/stacks.mjs +1 -2
- package/dist/ui/styles/base.mjs +1 -2
- package/dist/ui/styles/behavior.mjs +1 -2
- package/dist/ui/styles/review-layout.mjs +1 -2
- package/dist/ui/styles/review.mjs +1 -2
- package/dist/ui/styles/tokens.mjs +1 -2
- package/dist/ui/styles.mjs +1 -2
- package/dist/ui/subnav.mjs +1 -2
- package/dist/ui/table.mjs +1 -2
- package/dist/ui/theme.mjs +1 -2
- package/dist/ui/thumbnail.mjs +1 -2
- package/dist/ui/time.mjs +1 -2
- package/package.json +14 -12
- 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":"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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"}
|
|
@@ -4,24 +4,51 @@ import.meta.url;
|
|
|
4
4
|
import { Button } from "../ui/buttons.mjs";
|
|
5
5
|
import { Badge, statusTone } from "../ui/feedback.mjs";
|
|
6
6
|
import { Kbd } from "../ui/kbd.mjs";
|
|
7
|
+
import { groupOf, neighbours, snapshotPageUrl } from "./build-diff-model.mjs";
|
|
7
8
|
import { actionBar, actionGroup, actionHints, actionPosition } from "../ui/styles/review-layout.mjs";
|
|
8
|
-
import { neighbours, snapshotPageUrl } from "./build-diff-model.mjs";
|
|
9
9
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
10
|
-
|
|
11
|
-
function
|
|
12
|
-
return `/api/v1/projects/${project.slug}/builds/${build.id}
|
|
10
|
+
// src/pages/build-diff-actions.tsx
|
|
11
|
+
function apiBase(project, build) {
|
|
12
|
+
return `/api/v1/projects/${project.slug}/builds/${build.id}`;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
const isOpenStatus = (snap) => snap.status === "new" || snap.status === "changed";
|
|
15
|
+
function sharedTarget(base, group, open, name) {
|
|
16
|
+
return {
|
|
17
|
+
base: `${base}/stories/${encodeURIComponent(group.storyId)}/modes/${encodeURIComponent(group.modeKey)}`,
|
|
18
|
+
shared: true,
|
|
19
|
+
label: `${name} (${open.length} browsers)`
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function decisionTarget(props) {
|
|
23
|
+
const { project, build, snapshots, selected, drifted } = props;
|
|
24
|
+
const group = groupOf(snapshots, selected.id);
|
|
25
|
+
const open = (group?.members ?? [selected]).filter((snap) => isOpenStatus(snap));
|
|
26
|
+
const [first] = open;
|
|
27
|
+
if (!first) return null;
|
|
28
|
+
const base = apiBase(project, build);
|
|
29
|
+
const drift = { drifted: open.some((snap) => drifted[snap.id] !== void 0) };
|
|
30
|
+
if (open.length > 1 && group) return {
|
|
31
|
+
...sharedTarget(base, group, open, selected.storyName),
|
|
32
|
+
...drift
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
base: `${base}/snapshots/${first.id}`,
|
|
36
|
+
shared: false,
|
|
37
|
+
label: first.storyName,
|
|
38
|
+
...drift
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Approve (or "Approve anyway" when a baseline moved) as its own form. */
|
|
15
42
|
function ApproveForm(props) {
|
|
16
|
-
const {
|
|
17
|
-
const url = `${
|
|
43
|
+
const { target } = props;
|
|
44
|
+
const url = `${target.base}/approve${target.drifted ? "?force=true" : ""}`;
|
|
18
45
|
return /* @__PURE__ */ jsx("form", {
|
|
19
46
|
method: "post",
|
|
20
47
|
action: url,
|
|
21
48
|
"hx-post": url,
|
|
22
49
|
"hx-target": "body",
|
|
23
|
-
"data-toast": `Approved ${
|
|
24
|
-
children: drifted ? /* @__PURE__ */ jsx(Button, {
|
|
50
|
+
"data-toast": `Approved ${target.label}`,
|
|
51
|
+
children: target.drifted ? /* @__PURE__ */ jsx(Button, {
|
|
25
52
|
variant: "secondary",
|
|
26
53
|
size: "sm",
|
|
27
54
|
type: "submit",
|
|
@@ -34,34 +61,61 @@ function ApproveForm(props) {
|
|
|
34
61
|
type: "submit",
|
|
35
62
|
icon: "check",
|
|
36
63
|
"data-approve": true,
|
|
37
|
-
title: "Approve (a)",
|
|
38
|
-
children: "Approve"
|
|
64
|
+
title: target.shared ? "Approve every browser (a)" : "Approve (a)",
|
|
65
|
+
children: target.shared ? "Approve all browsers" : "Approve"
|
|
39
66
|
})
|
|
40
67
|
});
|
|
41
68
|
}
|
|
42
|
-
/**
|
|
43
|
-
function
|
|
69
|
+
/** Per-snapshot escape hatch: approve only the browser being viewed. */
|
|
70
|
+
function ApproveOnlyThisForm(props) {
|
|
44
71
|
const { project, build, selected } = props;
|
|
45
|
-
const
|
|
72
|
+
const url = `${apiBase(project, build)}/snapshots/${selected.id}/approve`;
|
|
73
|
+
return /* @__PURE__ */ jsx("form", {
|
|
74
|
+
method: "post",
|
|
75
|
+
action: url,
|
|
76
|
+
"hx-post": url,
|
|
77
|
+
"hx-target": "body",
|
|
78
|
+
"data-toast": `Approved ${selected.storyName} on ${selected.browser ?? "chromium"}`,
|
|
79
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
80
|
+
variant: "ghost",
|
|
81
|
+
size: "sm",
|
|
82
|
+
type: "submit",
|
|
83
|
+
title: "Approve only this browser",
|
|
84
|
+
children: ["Only ", selected.browser ?? "chromium"]
|
|
85
|
+
})
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** Approve/reject for a reviewable story/mode; both advance to the next open one. */
|
|
89
|
+
function DecisionButtons(props) {
|
|
90
|
+
const { project, build, selected, target } = props;
|
|
91
|
+
const selectedOpen = selected.status === "new" || selected.status === "changed";
|
|
46
92
|
return /* @__PURE__ */ jsxs("div", {
|
|
47
93
|
class: actionGroup,
|
|
48
|
-
children: [
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"data-
|
|
61
|
-
|
|
62
|
-
children:
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsx(ApproveForm, { target }),
|
|
96
|
+
target.shared && selectedOpen ? /* @__PURE__ */ jsx(ApproveOnlyThisForm, {
|
|
97
|
+
project,
|
|
98
|
+
build,
|
|
99
|
+
selected
|
|
100
|
+
}) : null,
|
|
101
|
+
/* @__PURE__ */ jsx("form", {
|
|
102
|
+
method: "post",
|
|
103
|
+
action: `${target.base}/reject`,
|
|
104
|
+
"hx-post": `${target.base}/reject`,
|
|
105
|
+
"hx-target": "body",
|
|
106
|
+
"data-toast": `Rejected ${target.label}`,
|
|
107
|
+
"data-toast-tone": "warning",
|
|
108
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
109
|
+
variant: "danger",
|
|
110
|
+
size: "sm",
|
|
111
|
+
type: "submit",
|
|
112
|
+
icon: "x",
|
|
113
|
+
"data-reject": true,
|
|
114
|
+
title: "Reject (r)",
|
|
115
|
+
children: "Reject"
|
|
116
|
+
})
|
|
63
117
|
})
|
|
64
|
-
|
|
118
|
+
]
|
|
65
119
|
});
|
|
66
120
|
}
|
|
67
121
|
function StepButton(props) {
|
|
@@ -91,9 +145,9 @@ function StepButton(props) {
|
|
|
91
145
|
}
|
|
92
146
|
/** Previous/next stepping, position, shortcut hints, and the review decision. */
|
|
93
147
|
function DiffActionBar(props) {
|
|
94
|
-
const { project, build, snapshots, selected, canReview
|
|
148
|
+
const { project, build, snapshots, selected, canReview } = props;
|
|
95
149
|
const around = neighbours(snapshots, selected.id);
|
|
96
|
-
const
|
|
150
|
+
const target = decisionTarget(props);
|
|
97
151
|
return /* @__PURE__ */ jsxs("div", {
|
|
98
152
|
class: actionBar,
|
|
99
153
|
role: "toolbar",
|
|
@@ -143,11 +197,11 @@ function DiffActionBar(props) {
|
|
|
143
197
|
})
|
|
144
198
|
]
|
|
145
199
|
}),
|
|
146
|
-
canReview &&
|
|
200
|
+
canReview && target ? /* @__PURE__ */ jsx(DecisionButtons, {
|
|
147
201
|
project,
|
|
148
202
|
build,
|
|
149
203
|
selected,
|
|
150
|
-
|
|
204
|
+
target
|
|
151
205
|
}) : /* @__PURE__ */ jsx(Badge, {
|
|
152
206
|
tone: statusTone(selected.status),
|
|
153
207
|
icon: true,
|
|
@@ -156,7 +210,6 @@ function DiffActionBar(props) {
|
|
|
156
210
|
]
|
|
157
211
|
});
|
|
158
212
|
}
|
|
159
|
-
//#endregion
|
|
160
213
|
export { DiffActionBar };
|
|
161
214
|
|
|
162
215
|
//# sourceMappingURL=build-diff-actions.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-diff-actions.mjs","names":[],"sources":["../../src/pages/build-diff-actions.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Badge, Button, Kbd, statusTone } from \"../ui/components.tsx\";\nimport { actionBar, actionGroup, actionHints, actionPosition } from \"../ui/styles/review-layout.ts\";\nimport { neighbours, snapshotPageUrl } from \"./build-diff-model.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Sticky bottom bar props. */\nexport interface DiffActionBarProps {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n selected: Snapshot;\n canReview: boolean;\n drifted: boolean;\n}\n\nfunction snapshotBase(project: Project, build: Build, snapshot: Snapshot): string {\n return `/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snapshot.id}`;\n}\n\ntype DecisionProps = { project: Project; build: Build; selected: Snapshot; drifted: boolean };\n\n/** Approve (or \"Approve anyway\" when the baseline moved) as its own form. */\nfunction ApproveForm(props: DecisionProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, selected, drifted } = props;\n const url = `${snapshotBase(project, build, selected)}/approve${drifted ? \"?force=true\" : \"\"}`;\n return (\n <form\n method=\"post\"\n action={url}\n hx-post={url}\n hx-target=\"body\"\n data-toast={`Approved ${selected.storyName}`}\n >\n {drifted ? (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n type=\"submit\"\n icon=\"check\"\n title=\"The baseline changed after this diff; replace it with this screenshot\"\n >\n Approve anyway\n </Button>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n type=\"submit\"\n icon=\"check\"\n data-approve\n title=\"Approve (a)\"\n >\n Approve\n </Button>\n )}\n </form>\n );\n}\n\n/** Approve/reject for a reviewable snapshot; both advance to the next open one. */\nfunction DecisionButtons(props: DecisionProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, selected } = props;\n const base = snapshotBase(project, build, selected);\n return (\n <div class={actionGroup}>\n <ApproveForm {...props} />\n <form\n method=\"post\"\n action={`${base}/reject`}\n hx-post={`${base}/reject`}\n hx-target=\"body\"\n data-toast={`Rejected ${selected.storyName}`}\n data-toast-tone=\"warning\"\n >\n <Button variant=\"danger\" size=\"sm\" type=\"submit\" icon=\"x\" data-reject title=\"Reject (r)\">\n Reject\n </Button>\n </form>\n </div>\n );\n}\n\nfunction StepButton(props: {\n target: Snapshot | null;\n project: Project;\n build: Build;\n direction: \"prev\" | \"next\";\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { target, project, build, direction } = props;\n const label = direction === \"prev\" ? \"Previous snapshot (k)\" : \"Next snapshot (j)\";\n const icon = direction === \"prev\" ? \"chevron-left\" : \"chevron-right\";\n if (!target) {\n return <Button variant=\"ghost\" size=\"sm\" icon={icon} disabled aria-label={label} />;\n }\n const url = snapshotPageUrl(project, build, target.id);\n const hook = direction === \"prev\" ? { \"data-snap-prev\": true } : { \"data-snap-next\": true };\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon={icon}\n href={url}\n aria-label={label}\n title={label}\n hx-get={url}\n hx-target=\"body\"\n hx-push-url=\"true\"\n {...hook}\n />\n );\n}\n\n/** Previous/next stepping, position, shortcut hints, and the review decision. */\nexport function DiffActionBar(\n props: DiffActionBarProps,\n): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, snapshots, selected, canReview, drifted } = props;\n const around = neighbours(snapshots, selected.id);\n const reviewable = selected.status === \"new\" || selected.status === \"changed\";\n return (\n <div class={actionBar} role=\"toolbar\" aria-label=\"Review actions\">\n <div class={actionGroup}>\n <StepButton target={around.prev} project={project} build={build} direction=\"prev\" />\n <span class={actionPosition} aria-live=\"polite\">\n {around.position} / {around.total}\n </span>\n <StepButton target={around.next} project={project} build={build} direction=\"next\" />\n </div>\n <div class={actionHints}>\n <Kbd keys={[\"j\", \"k\"]} /> navigate <Kbd keys=\"a\" /> approve <Kbd keys=\"r\" /> reject\n <Button variant=\"ghost\" size=\"sm\" data-shortcuts-open aria-label=\"Keyboard shortcuts (?)\">\n <Kbd keys=\"?\" />\n </Button>\n </div>\n {canReview && reviewable ? (\n <DecisionButtons project={project} build={build} selected={selected} drifted={drifted} />\n ) : (\n <Badge tone={statusTone(selected.status)} icon>\n {selected.status}\n </Badge>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build-diff-actions.mjs","names":[],"sources":["../../src/pages/build-diff-actions.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Badge, Button, Kbd, statusTone } from \"../ui/components.tsx\";\nimport { actionBar, actionGroup, actionHints, actionPosition } from \"../ui/styles/review-layout.ts\";\nimport { groupOf, neighbours, snapshotPageUrl, type SnapshotGroup } from \"./build-diff-model.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Sticky bottom bar props. */\nexport interface DiffActionBarProps {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n selected: Snapshot;\n canReview: boolean;\n /** Open snapshots whose baseline changed after their diff was computed. */\n drifted: Record<string, \"stale\" | \"removed\">;\n}\n\n/** What one decision covers: a whole story/mode across browsers, or a single snapshot. */\ninterface DecisionTarget {\n /** Endpoint prefix; `/approve` and `/reject` are appended. */\n base: string;\n /** True when the decision applies to every open browser of the story/mode. */\n shared: boolean;\n drifted: boolean;\n label: string;\n}\n\nfunction apiBase(project: Project, build: Build): string {\n return `/api/v1/projects/${project.slug}/builds/${build.id}`;\n}\n\nconst isOpenStatus = (snap: Snapshot): boolean =>\n snap.status === \"new\" || snap.status === \"changed\";\n\nfunction sharedTarget(\n base: string,\n group: SnapshotGroup,\n open: Snapshot[],\n name: string,\n): Pick<DecisionTarget, \"base\" | \"shared\" | \"label\"> {\n const story = encodeURIComponent(group.storyId);\n const mode = encodeURIComponent(group.modeKey);\n return {\n base: `${base}/stories/${story}/modes/${mode}`,\n shared: true,\n label: `${name} (${open.length} browsers)`,\n };\n}\n\nfunction decisionTarget(props: DiffActionBarProps): DecisionTarget | null {\n const { project, build, snapshots, selected, drifted } = props;\n const group = groupOf(snapshots, selected.id);\n const open = (group?.members ?? [selected]).filter((snap) => isOpenStatus(snap));\n const [first] = open;\n if (!first) return null;\n const base = apiBase(project, build);\n const drift = { drifted: open.some((snap) => drifted[snap.id] !== undefined) };\n if (open.length > 1 && group) {\n return { ...sharedTarget(base, group, open, selected.storyName), ...drift };\n }\n return { base: `${base}/snapshots/${first.id}`, shared: false, label: first.storyName, ...drift };\n}\n\n/** Approve (or \"Approve anyway\" when a baseline moved) as its own form. */\nfunction ApproveForm(props: {\n target: DecisionTarget;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { target } = props;\n const url = `${target.base}/approve${target.drifted ? \"?force=true\" : \"\"}`;\n return (\n <form\n method=\"post\"\n action={url}\n hx-post={url}\n hx-target=\"body\"\n data-toast={`Approved ${target.label}`}\n >\n {target.drifted ? (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n type=\"submit\"\n icon=\"check\"\n title=\"The baseline changed after this diff; replace it with this screenshot\"\n >\n Approve anyway\n </Button>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n type=\"submit\"\n icon=\"check\"\n data-approve\n title={target.shared ? \"Approve every browser (a)\" : \"Approve (a)\"}\n >\n {target.shared ? \"Approve all browsers\" : \"Approve\"}\n </Button>\n )}\n </form>\n );\n}\n\n/** Per-snapshot escape hatch: approve only the browser being viewed. */\nfunction ApproveOnlyThisForm(props: {\n project: Project;\n build: Build;\n selected: Snapshot;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, selected } = props;\n const url = `${apiBase(project, build)}/snapshots/${selected.id}/approve`;\n return (\n <form\n method=\"post\"\n action={url}\n hx-post={url}\n hx-target=\"body\"\n data-toast={`Approved ${selected.storyName} on ${selected.browser ?? \"chromium\"}`}\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\" title=\"Approve only this browser\">\n Only {selected.browser ?? \"chromium\"}\n </Button>\n </form>\n );\n}\n\n/** Approve/reject for a reviewable story/mode; both advance to the next open one. */\nfunction DecisionButtons(props: {\n project: Project;\n build: Build;\n selected: Snapshot;\n target: DecisionTarget;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, selected, target } = props;\n const selectedOpen = selected.status === \"new\" || selected.status === \"changed\";\n return (\n <div class={actionGroup}>\n <ApproveForm target={target} />\n {target.shared && selectedOpen ? (\n <ApproveOnlyThisForm project={project} build={build} selected={selected} />\n ) : null}\n <form\n method=\"post\"\n action={`${target.base}/reject`}\n hx-post={`${target.base}/reject`}\n hx-target=\"body\"\n data-toast={`Rejected ${target.label}`}\n data-toast-tone=\"warning\"\n >\n <Button variant=\"danger\" size=\"sm\" type=\"submit\" icon=\"x\" data-reject title=\"Reject (r)\">\n Reject\n </Button>\n </form>\n </div>\n );\n}\n\nfunction StepButton(props: {\n target: Snapshot | null;\n project: Project;\n build: Build;\n direction: \"prev\" | \"next\";\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { target, project, build, direction } = props;\n const label = direction === \"prev\" ? \"Previous snapshot (k)\" : \"Next snapshot (j)\";\n const icon = direction === \"prev\" ? \"chevron-left\" : \"chevron-right\";\n if (!target) {\n return <Button variant=\"ghost\" size=\"sm\" icon={icon} disabled aria-label={label} />;\n }\n const url = snapshotPageUrl(project, build, target.id);\n const hook = direction === \"prev\" ? { \"data-snap-prev\": true } : { \"data-snap-next\": true };\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon={icon}\n href={url}\n aria-label={label}\n title={label}\n hx-get={url}\n hx-target=\"body\"\n hx-push-url=\"true\"\n {...hook}\n />\n );\n}\n\n/** Previous/next stepping, position, shortcut hints, and the review decision. */\nexport function DiffActionBar(\n props: DiffActionBarProps,\n): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, snapshots, selected, canReview } = props;\n const around = neighbours(snapshots, selected.id);\n const target = decisionTarget(props);\n return (\n <div class={actionBar} role=\"toolbar\" aria-label=\"Review actions\">\n <div class={actionGroup}>\n <StepButton target={around.prev} project={project} build={build} direction=\"prev\" />\n <span class={actionPosition} aria-live=\"polite\">\n {around.position} / {around.total}\n </span>\n <StepButton target={around.next} project={project} build={build} direction=\"next\" />\n </div>\n <div class={actionHints}>\n <Kbd keys={[\"j\", \"k\"]} /> navigate <Kbd keys=\"a\" /> approve <Kbd keys=\"r\" /> reject\n <Button variant=\"ghost\" size=\"sm\" data-shortcuts-open aria-label=\"Keyboard shortcuts (?)\">\n <Kbd keys=\"?\" />\n </Button>\n </div>\n {canReview && target ? (\n <DecisionButtons project={project} build={build} selected={selected} target={target} />\n ) : (\n <Badge tone={statusTone(selected.status)} icon>\n {selected.status}\n </Badge>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -6,12 +6,13 @@ import { Badge, Meta } from "../ui/feedback.mjs";
|
|
|
6
6
|
import { TextareaField } from "../ui/forms.mjs";
|
|
7
7
|
import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
8
8
|
import { VStack } from "../ui/stacks.mjs";
|
|
9
|
+
import { csrfField } from "../ui/csrf-field.mjs";
|
|
9
10
|
import { reviewComment, reviewCommentActions, reviewCommentBody, reviewCommentHead } from "../ui/styles/review.mjs";
|
|
10
11
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
12
|
+
// src/pages/build-diff-comments.tsx
|
|
12
13
|
/** One comment with an optional resolve action. */
|
|
13
14
|
function CommentCard(props) {
|
|
14
|
-
const { project, build, comment, canReview } = props;
|
|
15
|
+
const { project, build, comment, canReview, anchor } = props;
|
|
15
16
|
return /* @__PURE__ */ jsxs("div", {
|
|
16
17
|
class: reviewComment,
|
|
17
18
|
children: [
|
|
@@ -20,6 +21,7 @@ function CommentCard(props) {
|
|
|
20
21
|
children: [
|
|
21
22
|
/* @__PURE__ */ jsx("strong", { children: comment.userId ?? "anonymous" }),
|
|
22
23
|
/* @__PURE__ */ jsxs("span", { children: ["· ", new Date(comment.createdAt).toLocaleString()] }),
|
|
24
|
+
anchor ?? null,
|
|
23
25
|
comment.resolved ? /* @__PURE__ */ jsx(Badge, {
|
|
24
26
|
tone: "success",
|
|
25
27
|
children: "resolved"
|
|
@@ -30,18 +32,18 @@ function CommentCard(props) {
|
|
|
30
32
|
class: reviewCommentBody,
|
|
31
33
|
children: comment.body
|
|
32
34
|
}),
|
|
33
|
-
!comment.resolved && canReview ? /* @__PURE__ */
|
|
35
|
+
!comment.resolved && canReview ? /* @__PURE__ */ jsxs("form", {
|
|
34
36
|
method: "post",
|
|
35
|
-
action: `/
|
|
36
|
-
"hx-post": `/
|
|
37
|
+
action: `/projects/${project.slug}/builds/${build.id}/comments/${comment.id}/resolve`,
|
|
38
|
+
"hx-post": `/projects/${project.slug}/builds/${build.id}/comments/${comment.id}/resolve`,
|
|
37
39
|
"hx-target": "body",
|
|
38
40
|
class: reviewCommentActions,
|
|
39
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
41
|
+
children: [csrfField(), /* @__PURE__ */ jsx(Button, {
|
|
40
42
|
variant: "ghost",
|
|
41
43
|
size: "sm",
|
|
42
44
|
type: "submit",
|
|
43
45
|
children: "Mark resolved"
|
|
44
|
-
})
|
|
46
|
+
})]
|
|
45
47
|
}) : null
|
|
46
48
|
]
|
|
47
49
|
}, comment.id);
|
|
@@ -49,13 +51,13 @@ function CommentCard(props) {
|
|
|
49
51
|
/** Reply form scoped to the selected snapshot. */
|
|
50
52
|
function CommentForm(props) {
|
|
51
53
|
const { project, build, selectedId } = props;
|
|
52
|
-
return /* @__PURE__ */
|
|
54
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
53
55
|
method: "post",
|
|
54
|
-
action: `/
|
|
55
|
-
"hx-post": `/
|
|
56
|
+
action: `/projects/${project.slug}/builds/${build.id}/comments`,
|
|
57
|
+
"hx-post": `/projects/${project.slug}/builds/${build.id}/comments`,
|
|
56
58
|
"hx-target": "body",
|
|
57
59
|
class: "mt-1",
|
|
58
|
-
children: /* @__PURE__ */ jsxs(VStack, { children: [
|
|
60
|
+
children: [csrfField(), /* @__PURE__ */ jsxs(VStack, { children: [
|
|
59
61
|
/* @__PURE__ */ jsx("input", {
|
|
60
62
|
type: "hidden",
|
|
61
63
|
name: "snapshotId",
|
|
@@ -73,13 +75,13 @@ function CommentForm(props) {
|
|
|
73
75
|
type: "submit",
|
|
74
76
|
children: "Comment"
|
|
75
77
|
}) })
|
|
76
|
-
] })
|
|
78
|
+
] })]
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
81
|
/** Threaded comments for the selected snapshot plus the reply form. */
|
|
80
82
|
function DiffComments(props) {
|
|
81
|
-
const { project, build, selectedId, comments, canReview } = props;
|
|
82
|
-
const visible = comments.filter((c) =>
|
|
83
|
+
const { project, build, selectedId, selectedStoryId, comments, canReview } = props;
|
|
84
|
+
const visible = comments.filter((c) => c.snapshotId ? c.snapshotId === selectedId : !c.storyId || c.storyId === selectedStoryId);
|
|
83
85
|
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
84
86
|
/* @__PURE__ */ jsx(SectionTitle, {
|
|
85
87
|
level: 3,
|
|
@@ -98,7 +100,6 @@ function DiffComments(props) {
|
|
|
98
100
|
})
|
|
99
101
|
] });
|
|
100
102
|
}
|
|
101
|
-
|
|
102
|
-
export { DiffComments };
|
|
103
|
+
export { CommentCard, DiffComments };
|
|
103
104
|
|
|
104
105
|
//# sourceMappingURL=build-diff-comments.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-diff-comments.mjs","names":[],"sources":["../../src/pages/build-diff-comments.tsx"],"sourcesContent":["import type { Build } from \"@storyshelf/core/schema\";\nimport type { Comment } from \"@storyshelf/core/schema\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport {\n Badge,\n Button,\n Card,\n Meta,\n SectionTitle,\n TextareaField,\n VStack,\n} from \"../ui/components.tsx\";\nimport {\n reviewComment,\n reviewCommentActions,\n reviewCommentBody,\n reviewCommentHead,\n} from \"../ui/styles/review.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Snapshot comment thread props. */\nexport interface DiffCommentsProps {\n project: Project;\n build: Build;\n selectedId: string;\n comments: Comment[];\n canReview: boolean;\n}\n\
|
|
1
|
+
{"version":3,"file":"build-diff-comments.mjs","names":[],"sources":["../../src/pages/build-diff-comments.tsx"],"sourcesContent":["import type { Build } from \"@storyshelf/core/schema\";\nimport type { Comment } from \"@storyshelf/core/schema\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport {\n Badge,\n Button,\n Card,\n Meta,\n SectionTitle,\n TextareaField,\n VStack,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\nimport {\n reviewComment,\n reviewCommentActions,\n reviewCommentBody,\n reviewCommentHead,\n} from \"../ui/styles/review.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Snapshot comment thread props. */\nexport interface DiffCommentsProps {\n project: Project;\n build: Build;\n selectedId: string;\n /** Story of the selected snapshot: its story comments show here too (ADR 0013, decision 2). */\n selectedStoryId?: string;\n comments: Comment[];\n canReview: boolean;\n}\n\n/** One comment's props: where it sits, and what its head shows beside the author. */\nexport interface CommentCardProps {\n project: Project;\n build: Build;\n comment: Comment;\n canReview: boolean;\n /** What the comment is anchored to (a story badge), shown in its head. */\n anchor?: unknown;\n}\n\n/** One comment with an optional resolve action. */\nexport function CommentCard(\n props: CommentCardProps,\n): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, comment, canReview, anchor } = props;\n return (\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 {anchor ?? null}\n {comment.resolved ? <Badge tone=\"success\">resolved</Badge> : null}\n </div>\n <p class={reviewCommentBody}>{comment.body}</p>\n {!comment.resolved && canReview ? (\n <form\n method=\"post\"\n action={`/projects/${project.slug}/builds/${build.id}/comments/${comment.id}/resolve`}\n hx-post={`/projects/${project.slug}/builds/${build.id}/comments/${comment.id}/resolve`}\n hx-target=\"body\"\n class={reviewCommentActions}\n >\n {csrfField()}\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Mark resolved\n </Button>\n </form>\n ) : null}\n </div>\n );\n}\n\ninterface CommentFormProps {\n project: Project;\n build: Build;\n selectedId: string;\n}\n\n/** Reply form scoped to the selected snapshot. */\nfunction CommentForm(props: CommentFormProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, selectedId } = props;\n return (\n <form\n method=\"post\"\n action={`/projects/${project.slug}/builds/${build.id}/comments`}\n hx-post={`/projects/${project.slug}/builds/${build.id}/comments`}\n hx-target=\"body\"\n class=\"mt-1\"\n >\n {csrfField()}\n <VStack>\n <input type=\"hidden\" name=\"snapshotId\" value={selectedId} />\n <TextareaField\n label=\"Add comment\"\n name=\"body\"\n rows={3}\n required\n placeholder=\"Leave feedback on this snapshot…\"\n />\n <div>\n <Button variant=\"primary\" type=\"submit\">\n Comment\n </Button>\n </div>\n </VStack>\n </form>\n );\n}\n\n/** Threaded comments for the selected snapshot plus the reply form. */\nexport function DiffComments(\n props: DiffCommentsProps,\n): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, selectedId, selectedStoryId, comments, canReview } = props;\n const visible = comments.filter((c) =>\n c.snapshotId ? c.snapshotId === selectedId : !c.storyId || c.storyId === selectedStoryId,\n );\n return (\n <Card>\n <SectionTitle level={3}>Comments</SectionTitle>\n <VStack>\n {visible.map((comment): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <CommentCard\n key={comment.id}\n project={project}\n build={build}\n comment={comment}\n canReview={canReview}\n />\n ))}\n {visible.length === 0 ? <Meta>No comments on this snapshot.</Meta> : null}\n </VStack>\n\n <CommentForm project={project} build={build} selectedId={selectedId} />\n </Card>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6CA,SAAgB,YACd,OACgD;CAChD,MAAM,EAAE,SAAS,OAAO,SAAS,WAAW,WAAW;CACvD,OACE,qBAAC,OAAD;EAAsB,OAAO;EAA7B,UAAA;GACE,qBAAC,OAAD;IAAK,OAAO;IAAZ,UAAA;KACE,oBAAC,UAAD,EAAA,UAAS,QAAQ,UAAU,YAAoB,CAAA;KAC/C,qBAAC,QAAD,EAAA,UAAA,CAAM,MAAG,IAAI,KAAK,QAAQ,SAAS,CAAC,CAAC,eAAe,CAAQ,EAAA,CAAA;KAC3D,UAAU;KACV,QAAQ,WAAW,oBAAC,OAAD;MAAO,MAAK;MAAU,UAAA;KAAe,CAAA,IAAI;IAC1D;;GACL,oBAAC,KAAD;IAAG,OAAO;IAAoB,UAAA,QAAQ;GAAQ,CAAA;GAC7C,CAAC,QAAQ,YAAY,YACpB,qBAAC,QAAD;IACE,QAAO;IACP,QAAQ,aAAa,QAAQ,KAAK,UAAU,MAAM,GAAG,YAAY,QAAQ,GAAG;IAC5E,WAAS,aAAa,QAAQ,KAAK,UAAU,MAAM,GAAG,YAAY,QAAQ,GAAG;IAC7E,aAAU;IACV,OAAO;IALT,UAAA,CAOG,UAAU,GACX,oBAAC,QAAD;KAAQ,SAAQ;KAAQ,MAAK;KAAK,MAAK;KAAS,UAAA;IAExC,CAAA,CACJ;GACJ,CAAA,IAAA;EACD;CAtBK,GAAA,QAAQ,EAsBb;AAET;;AASA,SAAS,YAAY,OAAyE;CAC5F,MAAM,EAAE,SAAS,OAAO,eAAe;CACvC,OACE,qBAAC,QAAD;EACE,QAAO;EACP,QAAQ,aAAa,QAAQ,KAAK,UAAU,MAAM,GAAG;EACrD,WAAS,aAAa,QAAQ,KAAK,UAAU,MAAM,GAAG;EACtD,aAAU;EACV,OAAM;EALR,UAAA,CAOG,UAAU,GACX,qBAAC,QAAD,EAAA,UAAA;GACE,oBAAC,SAAD;IAAO,MAAK;IAAS,MAAK;IAAa,OAAO;GAAa,CAAA;GAC3D,oBAAC,eAAD;IACE,OAAM;IACN,MAAK;IACL,MAAM;IACN,UAAA;IACA,aAAY;GACb,CAAA;GACD,oBAAC,OAAD,EAAA,UACE,oBAAC,QAAD;IAAQ,SAAQ;IAAU,MAAK;IAAS,UAAA;GAEhC,CAAA,EACL,CAAA;EACC,EAAA,CAAA,CACJ;;AAEV;;AAGA,SAAgB,aACd,OACgD;CAChD,MAAM,EAAE,SAAS,OAAO,YAAY,iBAAiB,UAAU,cAAc;CAC7E,MAAM,UAAU,SAAS,QAAQ,MAC/B,EAAE,aAAa,EAAE,eAAe,aAAa,CAAC,EAAE,WAAW,EAAE,YAAY,eAC3E;CACA,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,oBAAC,cAAD;GAAc,OAAO;GAAG,UAAA;EAAsB,CAAA;EAC9C,qBAAC,QAAD,EAAA,UAAA,CACG,QAAQ,KAAK,YACZ,oBAAC,aAAD;GAEW;GACF;GACE;GACE;EACZ,GALM,QAAQ,EAKd,CACF,GACA,QAAQ,WAAW,IAAI,oBAAC,MAAD,EAAA,UAAM,gCAAmC,CAAA,IAAI,IAC/D,EAAA,CAAA;EAER,oBAAC,aAAD;GAAsB;GAAgB;GAAmB;EAAa,CAAA;CAClE,EAAA,CAAA;AAEV"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { css } from "../ui/css.mjs";
|
|
5
|
+
import { HStack } from "../ui/stacks.mjs";
|
|
6
|
+
import { snapshotImageUrl } from "./build-diff-model.mjs";
|
|
7
|
+
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
8
|
+
// src/pages/build-diff-default.tsx
|
|
9
|
+
const comparisonImage = css`
|
|
10
|
+
/* default-comparison-image */
|
|
11
|
+
max-width: 100%;
|
|
12
|
+
`;
|
|
13
|
+
function DefaultComparisonView(props) {
|
|
14
|
+
const { project, build, snapshot, comparison } = props;
|
|
15
|
+
const params = new URLSearchParams({
|
|
16
|
+
reference: "default",
|
|
17
|
+
id: comparison.id,
|
|
18
|
+
version: comparison.version
|
|
19
|
+
});
|
|
20
|
+
const baseline = `${snapshotImageUrl(project, build, snapshot, "baseline")}?${params}`;
|
|
21
|
+
return /* @__PURE__ */ jsxs("details", { children: [
|
|
22
|
+
/* @__PURE__ */ jsxs("summary", { children: [
|
|
23
|
+
"Compare with current default branch (",
|
|
24
|
+
comparison.branch,
|
|
25
|
+
")"
|
|
26
|
+
] }),
|
|
27
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
28
|
+
"This is a secondary comparison. Approval still uses the primary comparison above. Default baseline updated ",
|
|
29
|
+
comparison.version,
|
|
30
|
+
"."
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsx("p", { children: comparison.environment === "different" ? "Recorded environment fingerprints differ; some components may be unknown and differences may be environmental." : comparison.environment === "unknown" ? "Capture environment information is unavailable." : "Recorded capture environment fingerprints match; some components may be unknown." }),
|
|
33
|
+
comparison.available ? /* @__PURE__ */ jsxs(HStack, {
|
|
34
|
+
align: "start",
|
|
35
|
+
children: [/* @__PURE__ */ jsx(BaselineImage, { src: baseline }), /* @__PURE__ */ jsxs("figure", { children: [/* @__PURE__ */ jsx("figcaption", { children: "This build" }), /* @__PURE__ */ jsx("img", {
|
|
36
|
+
src: snapshotImageUrl(project, build, snapshot, "image"),
|
|
37
|
+
alt: "This build's screenshot",
|
|
38
|
+
class: comparisonImage
|
|
39
|
+
})] })]
|
|
40
|
+
}) : /* @__PURE__ */ jsx("p", { children: "The current default-branch baseline image is unavailable." })
|
|
41
|
+
] });
|
|
42
|
+
}
|
|
43
|
+
function BaselineImage(props) {
|
|
44
|
+
return /* @__PURE__ */ jsxs("figure", { children: [/* @__PURE__ */ jsx("figcaption", { children: "Current default-branch baseline" }), /* @__PURE__ */ jsx("img", {
|
|
45
|
+
src: props.src,
|
|
46
|
+
alt: "Current default-branch baseline",
|
|
47
|
+
loading: "lazy",
|
|
48
|
+
class: comparisonImage
|
|
49
|
+
})] });
|
|
50
|
+
}
|
|
51
|
+
export { DefaultComparisonView };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=build-diff-default.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-diff-default.mjs","names":[],"sources":["../../src/pages/build-diff-default.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { DefaultComparison } from \"../routers/baseline-reference.ts\";\nimport { HStack } from \"../ui/components.tsx\";\nimport { css } from \"../ui/css.ts\";\nimport { snapshotImageUrl } from \"./build-diff-model.ts\";\n\nconst comparisonImage = css`\n /* default-comparison-image */\n max-width: 100%;\n`;\n\n/* eslint-disable promise-function-async -- Hono JSX components may return HTML or a promise */\nexport function DefaultComparisonView(props: {\n project: Project;\n build: Build;\n snapshot: Snapshot;\n comparison: DefaultComparison;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, snapshot, comparison } = props;\n const params = new URLSearchParams({\n reference: \"default\",\n id: comparison.id,\n version: comparison.version,\n });\n const baseline = `${snapshotImageUrl(project, build, snapshot, \"baseline\")}?${params}`;\n return (\n <details>\n <summary>Compare with current default branch ({comparison.branch})</summary>\n <p>\n This is a secondary comparison. Approval still uses the primary comparison above. Default\n baseline updated {comparison.version}.\n </p>\n <p>\n {comparison.environment === \"different\"\n ? \"Recorded environment fingerprints differ; some components may be unknown and differences may be environmental.\"\n : comparison.environment === \"unknown\"\n ? \"Capture environment information is unavailable.\"\n : \"Recorded capture environment fingerprints match; some components may be unknown.\"}\n </p>\n {comparison.available ? (\n <HStack align=\"start\">\n <BaselineImage src={baseline} />\n <figure>\n <figcaption>This build</figcaption>\n <img\n src={snapshotImageUrl(project, build, snapshot, \"image\")}\n alt=\"This build's screenshot\"\n class={comparisonImage}\n />\n </figure>\n </HStack>\n ) : (\n <p>The current default-branch baseline image is unavailable.</p>\n )}\n </details>\n );\n}\n\nfunction BaselineImage(props: { src: string }): HtmlEscapedString | Promise<HtmlEscapedString> {\n return (\n <figure>\n <figcaption>Current default-branch baseline</figcaption>\n <img\n src={props.src}\n alt=\"Current default-branch baseline\"\n loading=\"lazy\"\n class={comparisonImage}\n />\n </figure>\n );\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,kBAAkB,GAAG;;;;AAM3B,SAAgB,sBAAsB,OAKa;CACjD,MAAM,EAAE,SAAS,OAAO,UAAU,eAAe;CACjD,MAAM,SAAS,IAAI,gBAAgB;EACjC,WAAW;EACX,IAAI,WAAW;EACf,SAAS,WAAW;CACtB,CAAC;CACD,MAAM,WAAW,GAAG,iBAAiB,SAAS,OAAO,UAAU,UAAU,EAAE,GAAG;CAC9E,OACE,qBAAC,WAAD,EAAA,UAAA;EACE,qBAAC,WAAD,EAAA,UAAA;GAAS;GAAsC,WAAW;GAAO;EAAU,EAAA,CAAA;EAC3E,qBAAC,KAAD,EAAA,UAAA;GAAG;GAEiB,WAAW;GAAQ;EACpC,EAAA,CAAA;EACH,oBAAC,KAAD,EAAA,UACG,WAAW,gBAAgB,cACxB,mHACA,WAAW,gBAAgB,YACzB,oDACA,mFACL,CAAA;EACF,WAAW,YACV,qBAAC,QAAD;GAAQ,OAAM;GAAd,UAAA,CACE,oBAAC,eAAD,EAAe,KAAK,SAAW,CAAA,GAC/B,qBAAC,UAAD,EAAA,UAAA,CACE,oBAAC,cAAD,EAAA,UAAY,aAAsB,CAAA,GAClC,oBAAC,OAAD;IACE,KAAK,iBAAiB,SAAS,OAAO,UAAU,OAAO;IACvD,KAAI;IACJ,OAAO;GACR,CAAA,CACK,EAAA,CAAA,CACF;EAER,CAAA,IAAA,oBAAC,KAAD,EAAA,UAAG,4DAA4D,CAAA;CAE1D,EAAA,CAAA;AAEb;AAEA,SAAS,cAAc,OAAwE;CAC7F,OACE,qBAAC,UAAD,EAAA,UAAA,CACE,oBAAC,cAAD,EAAA,UAAY,kCAA2C,CAAA,GACvD,oBAAC,OAAD;EACE,KAAK,MAAM;EACX,KAAI;EACJ,SAAQ;EACR,OAAO;CACR,CAAA,CACK,EAAA,CAAA;AAEZ"}
|