@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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { Alert } from "../ui/feedback.mjs";
|
|
5
|
+
import { StaleBaselineNotice } from "./build-diff-stale.mjs";
|
|
6
|
+
import { environmentChangeOf, labelOf, summariseEnvironmentChanges } from "./environment-change.mjs";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
8
|
+
// src/pages/build-diff-environment.tsx
|
|
9
|
+
/**
|
|
10
|
+
* Build-level note: some snapshots were captured in a different environment than their baseline.
|
|
11
|
+
* The default branch migrates them without a diff; a feature branch still diffs them (ADR 0005).
|
|
12
|
+
*/
|
|
13
|
+
function EnvironmentChangeNotice(props) {
|
|
14
|
+
const summary = summariseEnvironmentChanges(props.snapshots);
|
|
15
|
+
if (!summary) return null;
|
|
16
|
+
const { count, fields, browserVersion } = summary;
|
|
17
|
+
const what = `the ${fields.join(", ")} changed${browserVersion ? ` (${browserVersion.from} → ${browserVersion.to})` : ""}`;
|
|
18
|
+
return props.migrated ? /* @__PURE__ */ jsxs(Alert, {
|
|
19
|
+
tone: "info",
|
|
20
|
+
title: "Capture environment changed",
|
|
21
|
+
children: [
|
|
22
|
+
count,
|
|
23
|
+
" ",
|
|
24
|
+
count === 1 ? "snapshot was" : "snapshots were",
|
|
25
|
+
" accepted without a pixel diff because ",
|
|
26
|
+
what,
|
|
27
|
+
". The same code renders differently here, so a diff would only show the environment. The baselines were updated to match."
|
|
28
|
+
]
|
|
29
|
+
}) : /* @__PURE__ */ jsxs(Alert, {
|
|
30
|
+
tone: "info",
|
|
31
|
+
title: "Capture environment changed",
|
|
32
|
+
children: [
|
|
33
|
+
count,
|
|
34
|
+
" ",
|
|
35
|
+
count === 1 ? "snapshot was" : "snapshots were",
|
|
36
|
+
" compared against a baseline from a different capture environment: ",
|
|
37
|
+
what,
|
|
38
|
+
". A difference may come from the environment rather than the code; once the default branch captures in the new environment, a rebuild compares like with like."
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Per-snapshot note shown above the viewer for a snapshot whose capture environment changed. */
|
|
43
|
+
function EnvironmentChangeSnapshotNotice(props) {
|
|
44
|
+
const change = environmentChangeOf(props.snapshot);
|
|
45
|
+
if (!change) return null;
|
|
46
|
+
const what = change.changed.map((field) => labelOf(field)).join(", ");
|
|
47
|
+
return props.migrated ? /* @__PURE__ */ jsxs(Alert, {
|
|
48
|
+
tone: "info",
|
|
49
|
+
title: "No diff: capture environment changed",
|
|
50
|
+
children: [
|
|
51
|
+
"This snapshot was not compared pixel by pixel. What changed: ",
|
|
52
|
+
what,
|
|
53
|
+
"."
|
|
54
|
+
]
|
|
55
|
+
}) : /* @__PURE__ */ jsxs(Alert, {
|
|
56
|
+
tone: "info",
|
|
57
|
+
title: "Capture environment changed",
|
|
58
|
+
children: [
|
|
59
|
+
"The baseline was captured in a different environment (",
|
|
60
|
+
what,
|
|
61
|
+
"), so a difference may come from the environment rather than the code."
|
|
62
|
+
]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Build-level notices above the review grid: stale baselines and environment migrations. */
|
|
66
|
+
function ReviewNotices(props) {
|
|
67
|
+
const { project, build, snapshots, drifted, canReview } = props;
|
|
68
|
+
const staleCount = Object.keys(drifted).length;
|
|
69
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [staleCount > 0 ? /* @__PURE__ */ jsx(StaleBaselineNotice, {
|
|
70
|
+
project,
|
|
71
|
+
build,
|
|
72
|
+
staleCount,
|
|
73
|
+
canReview
|
|
74
|
+
}) : null, /* @__PURE__ */ jsx(EnvironmentChangeNotice, {
|
|
75
|
+
snapshots,
|
|
76
|
+
migrated: build.isDefault
|
|
77
|
+
})] });
|
|
78
|
+
}
|
|
79
|
+
export { EnvironmentChangeNotice, EnvironmentChangeSnapshotNotice, ReviewNotices };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=build-diff-environment.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-diff-environment.mjs","names":[],"sources":["../../src/pages/build-diff-environment.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Alert } from \"../ui/components.tsx\";\nimport { StaleBaselineNotice } from \"./build-diff-stale.tsx\";\nimport { environmentChangeOf, labelOf, summariseEnvironmentChanges } from \"./environment-change.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/**\n * Build-level note: some snapshots were captured in a different environment than their baseline.\n * The default branch migrates them without a diff; a feature branch still diffs them (ADR 0005).\n */\nexport function EnvironmentChangeNotice(props: {\n snapshots: readonly Snapshot[];\n migrated: boolean;\n}): HtmlEscapedString | Promise<HtmlEscapedString> | null {\n const summary = summariseEnvironmentChanges(props.snapshots);\n if (!summary) {\n return null;\n }\n const { count, fields, browserVersion } = summary;\n const what = `the ${fields.join(\", \")} changed${\n browserVersion ? ` (${browserVersion.from} → ${browserVersion.to})` : \"\"\n }`;\n return props.migrated ? (\n <Alert tone=\"info\" title=\"Capture environment changed\">\n {count} {count === 1 ? \"snapshot was\" : \"snapshots were\"} accepted without a pixel diff\n because {what}. The same code renders differently here, so a diff would only show the\n environment. The baselines were updated to match.\n </Alert>\n ) : (\n <Alert tone=\"info\" title=\"Capture environment changed\">\n {count} {count === 1 ? \"snapshot was\" : \"snapshots were\"} compared against a baseline from a\n different capture environment: {what}. A difference may come from the environment rather than\n the code; once the default branch captures in the new environment, a rebuild compares like\n with like.\n </Alert>\n );\n}\n\n/** Per-snapshot note shown above the viewer for a snapshot whose capture environment changed. */\nexport function EnvironmentChangeSnapshotNotice(props: {\n snapshot: Pick<Snapshot, \"envChange\">;\n migrated: boolean;\n}): HtmlEscapedString | Promise<HtmlEscapedString> | null {\n const change = environmentChangeOf(props.snapshot);\n if (!change) {\n return null;\n }\n const what = change.changed.map((field) => labelOf(field)).join(\", \");\n return props.migrated ? (\n <Alert tone=\"info\" title=\"No diff: capture environment changed\">\n This snapshot was not compared pixel by pixel. What changed: {what}.\n </Alert>\n ) : (\n <Alert tone=\"info\" title=\"Capture environment changed\">\n The baseline was captured in a different environment ({what}), so a difference may come from\n the environment rather than the code.\n </Alert>\n );\n}\n\n/** Build-level notices above the review grid: stale baselines and environment migrations. */\nexport function ReviewNotices(props: {\n project: Project;\n build: Build;\n snapshots: readonly Snapshot[];\n drifted: Record<string, unknown>;\n canReview: boolean;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, snapshots, drifted, canReview } = props;\n const staleCount = Object.keys(drifted).length;\n return (\n <>\n {staleCount > 0 ? (\n <StaleBaselineNotice\n project={project}\n build={build}\n staleCount={staleCount}\n canReview={canReview}\n />\n ) : null}\n <EnvironmentChangeNotice snapshots={snapshots} migrated={build.isDefault} />\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,wBAAwB,OAGkB;CACxD,MAAM,UAAU,4BAA4B,MAAM,SAAS;CAC3D,IAAI,CAAC,SACH,OAAO;CAET,MAAM,EAAE,OAAO,QAAQ,mBAAmB;CAC1C,MAAM,OAAO,OAAO,OAAO,KAAK,IAAI,EAAE,UACpC,iBAAiB,KAAK,eAAe,KAAK,KAAK,eAAe,GAAG,KAAK;CAExE,OAAO,MAAM,WACX,qBAAC,OAAD;EAAO,MAAK;EAAO,OAAM;EAAzB,UAAA;GACG;GAAM;GAAE,UAAU,IAAI,iBAAiB;GAAiB;GAChD;GAAK;EAET;CAEP,CAAA,IAAA,qBAAC,OAAD;EAAO,MAAK;EAAO,OAAM;EAAzB,UAAA;GACG;GAAM;GAAE,UAAU,IAAI,iBAAiB;GAAiB;GACzB;GAAK;EAGhC;;AAEX;;AAGA,SAAgB,gCAAgC,OAGU;CACxD,MAAM,SAAS,oBAAoB,MAAM,QAAQ;CACjD,IAAI,CAAC,QACH,OAAO;CAET,MAAM,OAAO,OAAO,QAAQ,KAAK,UAAU,QAAQ,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;CACpE,OAAO,MAAM,WACX,qBAAC,OAAD;EAAO,MAAK;EAAO,OAAM;EAAzB,UAAA;GAAgE;GACA;GAAK;EAC9D;CAEP,CAAA,IAAA,qBAAC,OAAD;EAAO,MAAK;EAAO,OAAM;EAAzB,UAAA;GAAuD;GACE;GAAK;EAEvD;;AAEX;;AAGA,SAAgB,cAAc,OAMqB;CACjD,MAAM,EAAE,SAAS,OAAO,WAAW,SAAS,cAAc;CAC1D,MAAM,aAAa,OAAO,KAAK,OAAO,CAAC,CAAC;CACxC,OACE,qBAAA,UAAA,EAAA,UAAA,CACG,aAAa,IACZ,oBAAC,qBAAD;EACW;EACF;EACK;EACD;CACZ,CAAA,IACC,MACJ,oBAAC,yBAAD;EAAoC;EAAW,UAAU,MAAM;CAAY,CAAA,CAC3E,EAAA,CAAA;AAEN"}
|
|
@@ -5,10 +5,11 @@ import { Button } from "../ui/buttons.mjs";
|
|
|
5
5
|
import { Badge, Meta, statusTone } from "../ui/feedback.mjs";
|
|
6
6
|
import { Avatar } from "../ui/avatar.mjs";
|
|
7
7
|
import { Progress } from "../ui/progress.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { a11ySummaryOf, a11yUnavailableNote } from "./a11y-violations.mjs";
|
|
9
9
|
import { reviewProgress } from "./build-diff-model.mjs";
|
|
10
|
+
import { headerActions, headerMain, headerMeta, headerProgress, headerSide, headerTitle, reviewHeader } from "../ui/styles/review-layout.mjs";
|
|
10
11
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
12
|
+
// src/pages/build-diff-header.tsx
|
|
12
13
|
/** Approve-all / reject-all bulk actions (each asks for confirmation). */
|
|
13
14
|
function BulkActions({ project, build, pendingCount }) {
|
|
14
15
|
const base = `/api/v1/projects/${project.slug}/builds/${build.id}`;
|
|
@@ -46,7 +47,8 @@ function BulkActions({ project, build, pendingCount }) {
|
|
|
46
47
|
})
|
|
47
48
|
})] });
|
|
48
49
|
}
|
|
49
|
-
function HeaderTitle({ build }) {
|
|
50
|
+
function HeaderTitle({ build, snapshots }) {
|
|
51
|
+
const a11y = a11ySummaryOf(snapshots);
|
|
50
52
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("h1", {
|
|
51
53
|
class: headerTitle,
|
|
52
54
|
children: [
|
|
@@ -70,7 +72,20 @@ function HeaderTitle({ build }) {
|
|
|
70
72
|
size: "sm"
|
|
71
73
|
}) : null,
|
|
72
74
|
build.authorName ? /* @__PURE__ */ jsx("span", { children: build.authorName }) : null,
|
|
73
|
-
/* @__PURE__ */ jsx("span", { children: build.message ?? "No message" })
|
|
75
|
+
/* @__PURE__ */ jsx("span", { children: build.message ?? "No message" }),
|
|
76
|
+
a11y.findings > 0 ? /* @__PURE__ */ jsxs(Badge, {
|
|
77
|
+
tone: "warning",
|
|
78
|
+
icon: true,
|
|
79
|
+
children: [
|
|
80
|
+
"a11y ",
|
|
81
|
+
a11y.findings,
|
|
82
|
+
" (does not block)"
|
|
83
|
+
]
|
|
84
|
+
}) : null,
|
|
85
|
+
a11y.unavailable > 0 ? /* @__PURE__ */ jsx(Badge, {
|
|
86
|
+
tone: "neutral",
|
|
87
|
+
children: a11yUnavailableNote(a11y.unavailable)
|
|
88
|
+
}) : null
|
|
74
89
|
]
|
|
75
90
|
})] });
|
|
76
91
|
}
|
|
@@ -92,7 +107,10 @@ function DiffHeader(props) {
|
|
|
92
107
|
class: reviewHeader,
|
|
93
108
|
children: [/* @__PURE__ */ jsx("div", {
|
|
94
109
|
class: headerMain,
|
|
95
|
-
children: /* @__PURE__ */ jsx(HeaderTitle, {
|
|
110
|
+
children: /* @__PURE__ */ jsx(HeaderTitle, {
|
|
111
|
+
build,
|
|
112
|
+
snapshots
|
|
113
|
+
})
|
|
96
114
|
}), /* @__PURE__ */ jsxs("div", {
|
|
97
115
|
class: headerSide,
|
|
98
116
|
children: [/* @__PURE__ */ jsx(HeaderProgress, { snapshots }), /* @__PURE__ */ jsxs("div", {
|
|
@@ -111,7 +129,6 @@ function DiffHeader(props) {
|
|
|
111
129
|
})]
|
|
112
130
|
});
|
|
113
131
|
}
|
|
114
|
-
//#endregion
|
|
115
132
|
export { DiffHeader };
|
|
116
133
|
|
|
117
134
|
//# sourceMappingURL=build-diff-header.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-diff-header.mjs","names":[],"sources":["../../src/pages/build-diff-header.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Avatar, Badge, Button, Meta, Progress, statusTone } from \"../ui/components.tsx\";\nimport {\n headerActions,\n headerMain,\n headerMeta,\n headerProgress,\n headerSide,\n headerTitle,\n reviewHeader,\n} from \"../ui/styles/review-layout.ts\";\nimport { reviewProgress } from \"./build-diff-model.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Header props for the diff review page. */\nexport interface DiffHeaderProps {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n pendingCount: number;\n canReview: boolean;\n}\n\ninterface BulkProps {\n project: Project;\n build: Build;\n pendingCount: number;\n}\n\n/** Approve-all / reject-all bulk actions (each asks for confirmation). */\nfunction BulkActions({\n project,\n build,\n pendingCount,\n}: BulkProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const base = `/api/v1/projects/${project.slug}/builds/${build.id}`;\n return (\n <>\n <form\n method=\"post\"\n action={`${base}/approve-all`}\n hx-post={`${base}/approve-all`}\n hx-target=\"body\"\n hx-confirm={`Approve all ${pendingCount} remaining changes?`}\n data-toast={`Approved ${pendingCount} ${pendingCount === 1 ? \"change\" : \"changes\"}`}\n >\n <Button variant=\"primary\" size=\"sm\" type=\"submit\" icon=\"check\">\n Approve all ({pendingCount})\n </Button>\n </form>\n <form\n method=\"post\"\n action={`${base}/reject-all`}\n hx-post={`${base}/reject-all`}\n hx-target=\"body\"\n hx-confirm={`Reject all ${pendingCount} remaining changes?`}\n data-toast={`Rejected ${pendingCount} ${pendingCount === 1 ? \"change\" : \"changes\"}`}\n data-toast-tone=\"warning\"\n >\n <Button variant=\"secondary\" size=\"sm\" type=\"submit\">\n Reject all\n </Button>\n </form>\n </>\n );\n}\n\nfunction HeaderTitle({
|
|
1
|
+
{"version":3,"file":"build-diff-header.mjs","names":[],"sources":["../../src/pages/build-diff-header.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Avatar, Badge, Button, Meta, Progress, statusTone } from \"../ui/components.tsx\";\nimport {\n headerActions,\n headerMain,\n headerMeta,\n headerProgress,\n headerSide,\n headerTitle,\n reviewHeader,\n} from \"../ui/styles/review-layout.ts\";\nimport { a11ySummaryOf, a11yUnavailableNote } from \"./a11y-violations.tsx\";\nimport { reviewProgress } from \"./build-diff-model.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Header props for the diff review page. */\nexport interface DiffHeaderProps {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n pendingCount: number;\n canReview: boolean;\n}\n\ninterface BulkProps {\n project: Project;\n build: Build;\n pendingCount: number;\n}\n\n/** Approve-all / reject-all bulk actions (each asks for confirmation). */\nfunction BulkActions({\n project,\n build,\n pendingCount,\n}: BulkProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const base = `/api/v1/projects/${project.slug}/builds/${build.id}`;\n return (\n <>\n <form\n method=\"post\"\n action={`${base}/approve-all`}\n hx-post={`${base}/approve-all`}\n hx-target=\"body\"\n hx-confirm={`Approve all ${pendingCount} remaining changes?`}\n data-toast={`Approved ${pendingCount} ${pendingCount === 1 ? \"change\" : \"changes\"}`}\n >\n <Button variant=\"primary\" size=\"sm\" type=\"submit\" icon=\"check\">\n Approve all ({pendingCount})\n </Button>\n </form>\n <form\n method=\"post\"\n action={`${base}/reject-all`}\n hx-post={`${base}/reject-all`}\n hx-target=\"body\"\n hx-confirm={`Reject all ${pendingCount} remaining changes?`}\n data-toast={`Rejected ${pendingCount} ${pendingCount === 1 ? \"change\" : \"changes\"}`}\n data-toast-tone=\"warning\"\n >\n <Button variant=\"secondary\" size=\"sm\" type=\"submit\">\n Reject all\n </Button>\n </form>\n </>\n );\n}\n\nfunction HeaderTitle({\n build,\n snapshots,\n}: {\n build: Build;\n snapshots: Snapshot[];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const a11y = a11ySummaryOf(snapshots);\n return (\n <>\n <h1 class={headerTitle}>\n <span>{build.gitBranch}</span>\n <Meta as=\"span\" mono>\n {build.gitSha.slice(0, 7)}\n </Meta>\n <Badge tone={statusTone(build.status)} icon>\n {build.status}\n </Badge>\n </h1>\n <p class={headerMeta}>\n {build.authorName ? <Avatar name={build.authorName} size=\"sm\" /> : null}\n {build.authorName ? <span>{build.authorName}</span> : null}\n <span>{build.message ?? \"No message\"}</span>\n {a11y.findings > 0 ? (\n <Badge tone=\"warning\" icon>\n a11y {a11y.findings} (does not block)\n </Badge>\n ) : null}\n {a11y.unavailable > 0 ? (\n <Badge tone=\"neutral\">{a11yUnavailableNote(a11y.unavailable)}</Badge>\n ) : null}\n </p>\n </>\n );\n}\n\nfunction HeaderProgress({\n snapshots,\n}: {\n snapshots: Snapshot[];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const progress = reviewProgress(snapshots);\n return (\n <div class={headerProgress}>\n <span>\n {progress.reviewable === 0\n ? \"No changes to review\"\n : `${progress.done} of ${progress.reviewable} reviewed`}\n </span>\n <Progress value={progress.done} max={progress.reviewable} label=\"Review progress\" />\n </div>\n );\n}\n\n/** Compact build summary with review progress and bulk actions. */\nexport function DiffHeader(props: DiffHeaderProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, snapshots, pendingCount, canReview } = props;\n return (\n <header class={reviewHeader}>\n <div class={headerMain}>\n <HeaderTitle build={build} snapshots={snapshots} />\n </div>\n <div class={headerSide}>\n <HeaderProgress snapshots={snapshots} />\n <div class={headerActions}>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n href={`/projects/${project.slug}/builds/${build.id}`}\n >\n Build overview\n </Button>\n {canReview && pendingCount > 0 ? (\n <BulkActions project={project} build={build} pendingCount={pendingCount} />\n ) : null}\n </div>\n </div>\n </header>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiCA,SAAS,YAAY,EACnB,SACA,OACA,gBAC4D;CAC5D,MAAM,OAAO,oBAAoB,QAAQ,KAAK,UAAU,MAAM;CAC9D,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;EACE,QAAO;EACP,QAAQ,GAAG,KAAK;EAChB,WAAS,GAAG,KAAK;EACjB,aAAU;EACV,cAAY,eAAe,aAAa;EACxC,cAAY,YAAY,aAAa,GAAG,iBAAiB,IAAI,WAAW;EAExE,UAAA,qBAAC,QAAD;GAAQ,SAAQ;GAAU,MAAK;GAAK,MAAK;GAAS,MAAK;GAAvD,UAAA;IAA+D;IAC/C;IAAa;GACrB;;CACJ,CAAA,GACN,oBAAC,QAAD;EACE,QAAO;EACP,QAAQ,GAAG,KAAK;EAChB,WAAS,GAAG,KAAK;EACjB,aAAU;EACV,cAAY,cAAc,aAAa;EACvC,cAAY,YAAY,aAAa,GAAG,iBAAiB,IAAI,WAAW;EACxE,mBAAgB;EAEhB,UAAA,oBAAC,QAAD;GAAQ,SAAQ;GAAY,MAAK;GAAK,MAAK;GAAS,UAAA;EAE5C,CAAA;CACJ,CAAA,CACN,EAAA,CAAA;AAEN;AAEA,SAAS,YAAY,EACnB,OACA,aAIiD;CACjD,MAAM,OAAO,cAAc,SAAS;CACpC,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,MAAD;EAAI,OAAO;EAAX,UAAA;GACE,oBAAC,QAAD,EAAA,UAAO,MAAM,UAAgB,CAAA;GAC7B,oBAAC,MAAD;IAAM,IAAG;IAAO,MAAA;IACb,UAAA,MAAM,OAAO,MAAM,GAAG,CAAC;GACpB,CAAA;GACN,oBAAC,OAAD;IAAO,MAAM,WAAW,MAAM,MAAM;IAAG,MAAA;IACpC,UAAA,MAAM;GACF,CAAA;EACL;CACJ,CAAA,GAAA,qBAAC,KAAD;EAAG,OAAO;EAAV,UAAA;GACG,MAAM,aAAa,oBAAC,QAAD;IAAQ,MAAM,MAAM;IAAY,MAAK;GAAM,CAAA,IAAI;GAClE,MAAM,aAAa,oBAAC,QAAD,EAAA,UAAO,MAAM,WAAiB,CAAA,IAAI;GACtD,oBAAC,QAAD,EAAA,UAAO,MAAM,WAAW,aAAmB,CAAA;GAC1C,KAAK,WAAW,IACf,qBAAC,OAAD;IAAO,MAAK;IAAU,MAAA;IAAtB,UAAA;KAA2B;KACnB,KAAK;KAAS;IACf;GACL,CAAA,IAAA;GACH,KAAK,cAAc,IAClB,oBAAC,OAAD;IAAO,MAAK;IAAW,UAAA,oBAAoB,KAAK,WAAW;GAAS,CAAA,IAClE;EACH;CACH,CAAA,CAAA,EAAA,CAAA;AAEN;AAEA,SAAS,eAAe,EACtB,aAGiD;CACjD,MAAM,WAAW,eAAe,SAAS;CACzC,OACE,qBAAC,OAAD;EAAK,OAAO;EAAZ,UAAA,CACE,oBAAC,QAAD,EAAA,UACG,SAAS,eAAe,IACrB,yBACA,GAAG,SAAS,KAAK,MAAM,SAAS,WAAW,WAC3C,CAAA,GACN,oBAAC,UAAD;GAAU,OAAO,SAAS;GAAM,KAAK,SAAS;GAAY,OAAM;EAAmB,CAAA,CAChF;;AAET;;AAGA,SAAgB,WAAW,OAAwE;CACjG,MAAM,EAAE,SAAS,OAAO,WAAW,cAAc,cAAc;CAC/D,OACE,qBAAC,UAAD;EAAQ,OAAO;EAAf,UAAA,CACE,oBAAC,OAAD;GAAK,OAAO;GACV,UAAA,oBAAC,aAAD;IAAoB;IAAkB;GAAY,CAAA;EAC/C,CAAA,GACL,qBAAC,OAAD;GAAK,OAAO;GAAZ,UAAA,CACE,oBAAC,gBAAD,EAA2B,UAAY,CAAA,GACvC,qBAAC,OAAD;IAAK,OAAO;IAAZ,UAAA,CACE,oBAAC,QAAD;KACE,SAAQ;KACR,MAAK;KACL,MAAM,aAAa,QAAQ,KAAK,UAAU,MAAM;KACjD,UAAA;IAEO,CAAA,GACP,aAAa,eAAe,IAC3B,oBAAC,aAAD;KAAsB;KAAgB;KAAqB;IAAe,CAAA,IACxE,IACD;GACF,CAAA,CAAA;EACC,CAAA,CAAA;;AAEZ"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { Button } from "../ui/buttons.mjs";
|
|
5
|
+
import { Alert, Badge, Meta } from "../ui/feedback.mjs";
|
|
6
|
+
import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
7
|
+
import { HStack, VStack } from "../ui/stacks.mjs";
|
|
8
|
+
import { csrfField } from "../ui/csrf-field.mjs";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
10
|
+
// src/pages/build-diff-insight.tsx
|
|
11
|
+
function itemsOf(output) {
|
|
12
|
+
const items = output?.items;
|
|
13
|
+
return Array.isArray(items) ? items : [];
|
|
14
|
+
}
|
|
15
|
+
function verdictTone(verdict) {
|
|
16
|
+
if (verdict === "likely-intended") return "success";
|
|
17
|
+
if (verdict === "needs-review") return "warning";
|
|
18
|
+
return verdict === "likely-regression" ? "danger" : "neutral";
|
|
19
|
+
}
|
|
20
|
+
function InsightBody(props) {
|
|
21
|
+
const { latest } = props;
|
|
22
|
+
if (!latest) return /* @__PURE__ */ jsx(Meta, { children: "No AI triage yet for this build." });
|
|
23
|
+
if (latest.status === "failed") return /* @__PURE__ */ jsxs(Alert, {
|
|
24
|
+
tone: "warning",
|
|
25
|
+
title: "Triage failed",
|
|
26
|
+
children: [
|
|
27
|
+
"The model call did not finish (",
|
|
28
|
+
latest.errorCode,
|
|
29
|
+
"). You can try again."
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
if (latest.status !== "done") return /* @__PURE__ */ jsx(Meta, { children: "Working on it…" });
|
|
33
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
34
|
+
/* @__PURE__ */ jsx("span", { children: latest.summary }),
|
|
35
|
+
/* @__PURE__ */ jsx("ul", { children: itemsOf(latest.output).map((item) => /* @__PURE__ */ jsxs("li", { children: [
|
|
36
|
+
/* @__PURE__ */ jsx("strong", { children: item.snapshotKey }),
|
|
37
|
+
" (",
|
|
38
|
+
item.severity,
|
|
39
|
+
"): ",
|
|
40
|
+
item.note
|
|
41
|
+
] })) }),
|
|
42
|
+
/* @__PURE__ */ jsxs(Meta, { children: [
|
|
43
|
+
latest.profile,
|
|
44
|
+
" · ",
|
|
45
|
+
latest.model,
|
|
46
|
+
" · ",
|
|
47
|
+
latest.createdAt.slice(0, 16).replace("T", " ")
|
|
48
|
+
] })
|
|
49
|
+
] });
|
|
50
|
+
}
|
|
51
|
+
/** AI triage panel: verdict, summary, per-snapshot notes (plain text) and regenerate. */
|
|
52
|
+
function InsightPanel(props) {
|
|
53
|
+
const { project, build, data } = props;
|
|
54
|
+
const base = `/projects/${project.slug}/builds/${build.id}/insights`;
|
|
55
|
+
const latest = data.latest;
|
|
56
|
+
const inFlight = latest?.status === "pending" || latest?.status === "running";
|
|
57
|
+
const poll = inFlight ? {
|
|
58
|
+
"hx-get": `${base}/panel`,
|
|
59
|
+
"hx-trigger": "load delay:3s",
|
|
60
|
+
"hx-swap": "outerHTML"
|
|
61
|
+
} : {};
|
|
62
|
+
return /* @__PURE__ */ jsx("div", {
|
|
63
|
+
id: "insight-panel",
|
|
64
|
+
...poll,
|
|
65
|
+
children: /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsxs(HStack, { children: [
|
|
66
|
+
/* @__PURE__ */ jsx(SectionTitle, {
|
|
67
|
+
level: 3,
|
|
68
|
+
children: "AI triage (advisory)"
|
|
69
|
+
}),
|
|
70
|
+
latest?.status === "done" ? /* @__PURE__ */ jsx(Badge, {
|
|
71
|
+
tone: verdictTone(latest.verdict),
|
|
72
|
+
children: latest.verdict
|
|
73
|
+
}) : null,
|
|
74
|
+
inFlight ? /* @__PURE__ */ jsx(Badge, {
|
|
75
|
+
tone: "info",
|
|
76
|
+
children: "generating…"
|
|
77
|
+
}) : null
|
|
78
|
+
] }), /* @__PURE__ */ jsxs(VStack, { children: [/* @__PURE__ */ jsx(InsightBody, { latest }), data.canGenerate && !inFlight ? /* @__PURE__ */ jsxs("form", {
|
|
79
|
+
method: "post",
|
|
80
|
+
action: `${base}/generate`,
|
|
81
|
+
"hx-post": `${base}/generate`,
|
|
82
|
+
"hx-target": "#insight-panel",
|
|
83
|
+
"hx-swap": "outerHTML",
|
|
84
|
+
children: [
|
|
85
|
+
csrfField(),
|
|
86
|
+
/* @__PURE__ */ jsx("input", {
|
|
87
|
+
type: "hidden",
|
|
88
|
+
name: "force",
|
|
89
|
+
value: latest ? "true" : "false"
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ jsx(Button, {
|
|
92
|
+
variant: "secondary",
|
|
93
|
+
size: "sm",
|
|
94
|
+
type: "submit",
|
|
95
|
+
icon: "sparkles",
|
|
96
|
+
children: latest ? "Regenerate" : "Generate triage"
|
|
97
|
+
})
|
|
98
|
+
]
|
|
99
|
+
}) : null] })] })
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Renders the panel when AI triage is available for the project, nothing otherwise. */
|
|
103
|
+
function InsightSlot(props) {
|
|
104
|
+
return props.data ? /* @__PURE__ */ jsx(InsightPanel, {
|
|
105
|
+
project: props.project,
|
|
106
|
+
build: props.build,
|
|
107
|
+
data: props.data
|
|
108
|
+
}) : null;
|
|
109
|
+
}
|
|
110
|
+
export { InsightPanel, InsightSlot };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=build-diff-insight.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-diff-insight.mjs","names":[],"sources":["../../src/pages/build-diff-insight.tsx"],"sourcesContent":["import type { Build, Project } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { InsightPanelData } from \"../insights/panel.ts\";\nimport {\n Alert,\n Badge,\n Button,\n Card,\n HStack,\n Meta,\n SectionTitle,\n VStack,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ninterface TriageItem {\n snapshotKey: string;\n note: string;\n severity: string;\n}\n\nfunction itemsOf(output: unknown): TriageItem[] {\n const items = (output as { items?: unknown } | null)?.items;\n return Array.isArray(items) ? (items as TriageItem[]) : [];\n}\n\nfunction verdictTone(verdict: string | null): \"success\" | \"warning\" | \"danger\" | \"neutral\" {\n if (verdict === \"likely-intended\") {\n return \"success\";\n }\n if (verdict === \"needs-review\") {\n return \"warning\";\n }\n return verdict === \"likely-regression\" ? \"danger\" : \"neutral\";\n}\n\nfunction InsightBody(props: {\n latest: InsightPanelData[\"latest\"];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { latest } = props;\n if (!latest) {\n return <Meta>No AI triage yet for this build.</Meta>;\n }\n if (latest.status === \"failed\") {\n return (\n <Alert tone=\"warning\" title=\"Triage failed\">\n The model call did not finish ({latest.errorCode}). You can try again.\n </Alert>\n );\n }\n if (latest.status !== \"done\") {\n return <Meta>Working on it…</Meta>;\n }\n return (\n <>\n <span>{latest.summary}</span>\n <ul>\n {itemsOf(latest.output).map((item) => (\n <li>\n <strong>{item.snapshotKey}</strong> ({item.severity}): {item.note}\n </li>\n ))}\n </ul>\n <Meta>\n {latest.profile} · {latest.model} · {latest.createdAt.slice(0, 16).replace(\"T\", \" \")}\n </Meta>\n </>\n );\n}\n\n/** Props for the AI triage panel on the build review page. */\nexport interface InsightPanelProps {\n project: Project;\n build: Build;\n data: InsightPanelData;\n}\n\n/** AI triage panel: verdict, summary, per-snapshot notes (plain text) and regenerate. */\nexport function InsightPanel(\n props: InsightPanelProps,\n): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, data } = props;\n const base = `/projects/${project.slug}/builds/${build.id}/insights`;\n const latest = data.latest;\n const inFlight = latest?.status === \"pending\" || latest?.status === \"running\";\n const poll = inFlight\n ? { \"hx-get\": `${base}/panel`, \"hx-trigger\": \"load delay:3s\", \"hx-swap\": \"outerHTML\" }\n : {};\n return (\n <div id=\"insight-panel\" {...poll}>\n <Card>\n <HStack>\n <SectionTitle level={3}>AI triage (advisory)</SectionTitle>\n {latest?.status === \"done\" ? (\n <Badge tone={verdictTone(latest.verdict)}>{latest.verdict}</Badge>\n ) : null}\n {inFlight ? <Badge tone=\"info\">generating…</Badge> : null}\n </HStack>\n <VStack>\n <InsightBody latest={latest} />\n {data.canGenerate && !inFlight ? (\n <form\n method=\"post\"\n action={`${base}/generate`}\n hx-post={`${base}/generate`}\n hx-target=\"#insight-panel\"\n hx-swap=\"outerHTML\"\n >\n {csrfField()}\n <input type=\"hidden\" name=\"force\" value={latest ? \"true\" : \"false\"} />\n <Button variant=\"secondary\" size=\"sm\" type=\"submit\" icon=\"sparkles\">\n {latest ? \"Regenerate\" : \"Generate triage\"}\n </Button>\n </form>\n ) : null}\n </VStack>\n </Card>\n </div>\n );\n}\n\n/** Renders the panel when AI triage is available for the project, nothing otherwise. */\nexport function InsightSlot(props: {\n project: Project;\n build: Build;\n data: InsightPanelData | null | undefined;\n}): HtmlEscapedString | Promise<HtmlEscapedString> | null {\n return props.data ? (\n <InsightPanel project={props.project} build={props.build} data={props.data} />\n ) : null;\n}\n"],"mappings":";;;;;;;;;;AAuBA,SAAS,QAAQ,QAA+B;CAC9C,MAAM,QAAS,QAAuC;CACtD,OAAO,MAAM,QAAQ,KAAK,IAAK,QAAyB,CAAC;AAC3D;AAEA,SAAS,YAAY,SAAsE;CACzF,IAAI,YAAY,mBACd,OAAO;CAET,IAAI,YAAY,gBACd,OAAO;CAET,OAAO,YAAY,sBAAsB,WAAW;AACtD;AAEA,SAAS,YAAY,OAE8B;CACjD,MAAM,EAAE,WAAW;CACnB,IAAI,CAAC,QACH,OAAO,oBAAC,MAAD,EAAA,UAAM,mCAAsC,CAAA;CAErD,IAAI,OAAO,WAAW,UACpB,OACE,qBAAC,OAAD;EAAO,MAAK;EAAU,OAAM;EAA5B,UAAA;GAA4C;GACV,OAAO;GAAU;EAC5C;;CAGX,IAAI,OAAO,WAAW,QACpB,OAAO,oBAAC,MAAD,EAAA,UAAM,iBAAoB,CAAA;CAEnC,OACE,qBAAA,UAAA,EAAA,UAAA;EACE,oBAAC,QAAD,EAAA,UAAO,OAAO,QAAc,CAAA;EAC5B,oBAAC,MAAD,EAAA,UACG,QAAQ,OAAO,MAAM,CAAC,CAAC,KAAK,SAC3B,qBAAC,MAAD,EAAA,UAAA;GACE,oBAAC,UAAD,EAAA,UAAS,KAAK,YAAoB,CAAA;GAAC;GAAG,KAAK;GAAS;GAAI,KAAK;EAC3D,EAAA,CAAA,CACL,EACC,CAAA;EACJ,qBAAC,MAAD,EAAA,UAAA;GACG,OAAO;GAAQ;GAAI,OAAO;GAAM;GAAI,OAAO,UAAU,MAAM,GAAG,EAAE,CAAC,CAAC,QAAQ,KAAK,GAAG;EAC/E,EAAA,CAAA;CACN,EAAA,CAAA;AAEN;;AAUA,SAAgB,aACd,OACgD;CAChD,MAAM,EAAE,SAAS,OAAO,SAAS;CACjC,MAAM,OAAO,aAAa,QAAQ,KAAK,UAAU,MAAM,GAAG;CAC1D,MAAM,SAAS,KAAK;CACpB,MAAM,WAAW,QAAQ,WAAW,aAAa,QAAQ,WAAW;CACpE,MAAM,OAAO,WACT;EAAE,UAAU,GAAG,KAAK;EAAS,cAAc;EAAiB,WAAW;CAAY,IACnF,CAAC;CACL,OACE,oBAAC,OAAD;EAAK,IAAG;EAAgB,GAAI;EAC1B,UAAA,qBAAC,MAAD,EAAA,UAAA,CACE,qBAAC,QAAD,EAAA,UAAA;GACE,oBAAC,cAAD;IAAc,OAAO;IAAG,UAAA;GAAkC,CAAA;GACzD,QAAQ,WAAW,SAClB,oBAAC,OAAD;IAAO,MAAM,YAAY,OAAO,OAAO;IAAI,UAAA,OAAO;GAAe,CAAA,IAC/D;GACH,WAAW,oBAAC,OAAD;IAAO,MAAK;IAAO,UAAA;GAAkB,CAAA,IAAI;EAC/C,EAAA,CAAA,GACR,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,aAAD,EAAqB,OAAS,CAAA,GAC7B,KAAK,eAAe,CAAC,WACpB,qBAAC,QAAD;GACE,QAAO;GACP,QAAQ,GAAG,KAAK;GAChB,WAAS,GAAG,KAAK;GACjB,aAAU;GACV,WAAQ;GALV,UAAA;IAOG,UAAU;IACX,oBAAC,SAAD;KAAO,MAAK;KAAS,MAAK;KAAQ,OAAO,SAAS,SAAS;IAAU,CAAA;IACrE,oBAAC,QAAD;KAAQ,SAAQ;KAAY,MAAK;KAAK,MAAK;KAAS,MAAK;KACtD,UAAA,SAAS,eAAe;IACnB,CAAA;GACJ;EACJ,CAAA,IAAA,IACE,EAAA,CAAA,CACJ,EAAA,CAAA;CACH,CAAA;AAET;;AAGA,SAAgB,YAAY,OAI8B;CACxD,OAAO,MAAM,OACX,oBAAC,cAAD;EAAc,SAAS,MAAM;EAAS,OAAO,MAAM;EAAO,MAAM,MAAM;CAAO,CAAA,IAC3E;AACN"}
|
|
@@ -1,35 +1,75 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/pages/build-diff-model.ts
|
|
5
5
|
const OPEN = /* @__PURE__ */ new Set(["new", "changed"]);
|
|
6
6
|
const DECIDED = /* @__PURE__ */ new Set(["approved", "rejected"]);
|
|
7
|
-
|
|
7
|
+
const browserOf = (snap) => snap.browser ?? "chromium";
|
|
8
|
+
function memberOrder(a, b) {
|
|
9
|
+
const left = browserOf(a);
|
|
10
|
+
const right = browserOf(b);
|
|
11
|
+
if (left === right) return 0;
|
|
12
|
+
if (left === "chromium") return -1;
|
|
13
|
+
if (right === "chromium") return 1;
|
|
14
|
+
return left < right ? -1 : 1;
|
|
15
|
+
}
|
|
16
|
+
/** Group snapshots by story and mode, keeping first-appearance order. */
|
|
17
|
+
function groupSnapshots(snapshots) {
|
|
18
|
+
const groups = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const snap of snapshots) {
|
|
20
|
+
const key = `${snap.storyId}\u0000${snap.viewportName}`;
|
|
21
|
+
groups.set(key, [...groups.get(key) ?? [], snap]);
|
|
22
|
+
}
|
|
23
|
+
return [...groups.entries()].flatMap(([key, list]) => {
|
|
24
|
+
const members = list.toSorted(memberOrder);
|
|
25
|
+
const [primary] = members;
|
|
26
|
+
if (!primary) return [];
|
|
27
|
+
return [{
|
|
28
|
+
key,
|
|
29
|
+
storyId: primary.storyId,
|
|
30
|
+
modeKey: primary.viewportName,
|
|
31
|
+
members,
|
|
32
|
+
primary,
|
|
33
|
+
representative: members.find((snap) => OPEN.has(snap.status)) ?? primary
|
|
34
|
+
}];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Whether a group still has a snapshot waiting for a decision. */
|
|
38
|
+
function isOpenGroup(group) {
|
|
39
|
+
return group.members.some((snap) => OPEN.has(snap.status));
|
|
40
|
+
}
|
|
41
|
+
/** Count review progress per group; groups of unchanged snapshots need no decision. */
|
|
8
42
|
function reviewProgress(snapshots) {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
43
|
+
const groups = groupSnapshots(snapshots);
|
|
44
|
+
const pending = groups.filter((group) => isOpenGroup(group)).length;
|
|
45
|
+
const done = groups.filter((group) => !isOpenGroup(group) && group.members.some((snap) => DECIDED.has(snap.status))).length;
|
|
11
46
|
return {
|
|
12
47
|
reviewable: pending + done,
|
|
13
48
|
done,
|
|
14
49
|
pending
|
|
15
50
|
};
|
|
16
51
|
}
|
|
17
|
-
/** Previous/next snapshot around the selected
|
|
52
|
+
/** Previous/next group (as its representative snapshot) around the selected snapshot. */
|
|
18
53
|
function neighbours(snapshots, selectedId) {
|
|
19
|
-
const
|
|
54
|
+
const groups = groupSnapshots(snapshots);
|
|
55
|
+
const index = groups.findIndex((group) => group.members.some((snap) => snap.id === selectedId));
|
|
20
56
|
if (index === -1) return {
|
|
21
57
|
position: 0,
|
|
22
|
-
total:
|
|
58
|
+
total: groups.length,
|
|
23
59
|
prev: null,
|
|
24
60
|
next: null
|
|
25
61
|
};
|
|
26
62
|
return {
|
|
27
63
|
position: index + 1,
|
|
28
|
-
total:
|
|
29
|
-
prev:
|
|
30
|
-
next:
|
|
64
|
+
total: groups.length,
|
|
65
|
+
prev: groups[index - 1]?.representative ?? null,
|
|
66
|
+
next: groups[index + 1]?.representative ?? null
|
|
31
67
|
};
|
|
32
68
|
}
|
|
69
|
+
/** The group a snapshot belongs to. */
|
|
70
|
+
function groupOf(snapshots, snapshotId) {
|
|
71
|
+
return groupSnapshots(snapshots).find((group) => group.members.some((snap) => snap.id === snapshotId));
|
|
72
|
+
}
|
|
33
73
|
/** Diff size as a percentage string ("3.1%"), or null when not computed. */
|
|
34
74
|
function diffPercent(snapshot) {
|
|
35
75
|
return snapshot.diffRatio === null || snapshot.diffRatio === void 0 ? null : `${(snapshot.diffRatio * 100).toFixed(1)}%`;
|
|
@@ -42,7 +82,29 @@ function snapshotPageUrl(project, build, snapshotId) {
|
|
|
42
82
|
function snapshotImageUrl(project, build, snapshot, kind) {
|
|
43
83
|
return `/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snapshot.id}/${kind}`;
|
|
44
84
|
}
|
|
45
|
-
|
|
46
|
-
|
|
85
|
+
/** Mode and engine of a snapshot for labels: `dark`, `dark · firefox` (chromium is implicit). */
|
|
86
|
+
function snapshotVariant(snap) {
|
|
87
|
+
const browser = snap.browser ?? "chromium";
|
|
88
|
+
return browser === "chromium" ? snap.viewportName : `${snap.viewportName} · ${browser}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The design links of a component that have a cached render, as comparison sources.
|
|
92
|
+
* A link without a render is not offered (the design strip still lists it).
|
|
93
|
+
*/
|
|
94
|
+
function designSources(items) {
|
|
95
|
+
const rendered = items.flatMap((item) => item.renderUrl === null ? [] : [{
|
|
96
|
+
item,
|
|
97
|
+
src: item.renderUrl
|
|
98
|
+
}]);
|
|
99
|
+
const several = rendered.length > 1;
|
|
100
|
+
return rendered.map(({ item, src }, index) => ({
|
|
101
|
+
value: several ? `design-${index + 1}` : "design",
|
|
102
|
+
label: several ? `${item.providerName} ${index + 1}` : "Design",
|
|
103
|
+
src,
|
|
104
|
+
name: item.name,
|
|
105
|
+
providerName: item.providerName
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
export { designSources, diffPercent, groupOf, groupSnapshots, isOpenGroup, neighbours, reviewProgress, snapshotImageUrl, snapshotPageUrl, snapshotVariant };
|
|
47
109
|
|
|
48
110
|
//# sourceMappingURL=build-diff-model.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-diff-model.mjs","names":[],"sources":["../../src/pages/build-diff-model.ts"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\n\n/** How far a build's review has progressed. */\nexport interface ReviewProgress {\n /**
|
|
1
|
+
{"version":3,"file":"build-diff-model.mjs","names":[],"sources":["../../src/pages/build-diff-model.ts"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { DesignItem } from \"./design-links.tsx\";\n\n/** How far a build's review has progressed, counted per story/mode (not per browser). */\nexport interface ReviewProgress {\n /** Groups a human has to decide on (any open or decided member). */\n reviewable: number;\n /** Reviewable groups with no open member left. */\n done: number;\n /** Groups still waiting for a decision. */\n pending: number;\n}\n\nconst OPEN = new Set([\"new\", \"changed\"]);\nconst DECIDED = new Set([\"approved\", \"rejected\"]);\n\n/**\n * One story in one mode, with its snapshot on every engine. Approval is shared:\n * a single decision covers all members.\n */\nexport interface SnapshotGroup {\n /** Stable identity (story + mode). */\n key: string;\n storyId: string;\n /** The mode key (`viewportName`). */\n modeKey: string;\n /** Member snapshots, chromium first, then by engine name. */\n members: Snapshot[];\n /** The primary engine's snapshot (chromium when present). */\n primary: Snapshot;\n /** Snapshot the filmstrip links to: first open member, else the first member. */\n representative: Snapshot;\n}\n\nconst browserOf = (snap: Pick<Snapshot, \"browser\">): string => snap.browser ?? \"chromium\";\n\nfunction memberOrder(a: Snapshot, b: Snapshot): number {\n const left = browserOf(a);\n const right = browserOf(b);\n if (left === right) return 0;\n if (left === \"chromium\") return -1;\n if (right === \"chromium\") return 1;\n return left < right ? -1 : 1;\n}\n\n/** Group snapshots by story and mode, keeping first-appearance order. */\nexport function groupSnapshots(snapshots: Snapshot[]): SnapshotGroup[] {\n const groups = new Map<string, Snapshot[]>();\n for (const snap of snapshots) {\n const key = `${snap.storyId}\\u0000${snap.viewportName}`;\n groups.set(key, [...(groups.get(key) ?? []), snap]);\n }\n return [...groups.entries()].flatMap(([key, list]): SnapshotGroup[] => {\n const members = list.toSorted(memberOrder);\n const [primary] = members;\n if (!primary) return [];\n return [\n {\n key,\n storyId: primary.storyId,\n modeKey: primary.viewportName,\n members,\n primary,\n representative: members.find((snap) => OPEN.has(snap.status)) ?? primary,\n },\n ];\n });\n}\n\n/** Whether a group still has a snapshot waiting for a decision. */\nexport function isOpenGroup(group: SnapshotGroup): boolean {\n return group.members.some((snap) => OPEN.has(snap.status));\n}\n\n/** Count review progress per group; groups of unchanged snapshots need no decision. */\nexport function reviewProgress(snapshots: Snapshot[]): ReviewProgress {\n const groups = groupSnapshots(snapshots);\n const pending = groups.filter((group) => isOpenGroup(group)).length;\n const done = groups.filter(\n (group) => !isOpenGroup(group) && group.members.some((snap) => DECIDED.has(snap.status)),\n ).length;\n return { reviewable: pending + done, done, pending };\n}\n\n/** Position of the selected group plus its neighbours in list order. */\nexport interface Neighbours {\n /** 1-based group position, or 0 when nothing is selected. */\n position: number;\n /** Number of groups. */\n total: number;\n prev: Snapshot | null;\n next: Snapshot | null;\n}\n\n/** Previous/next group (as its representative snapshot) around the selected snapshot. */\nexport function neighbours(snapshots: Snapshot[], selectedId?: string): Neighbours {\n const groups = groupSnapshots(snapshots);\n const index = groups.findIndex((group) => group.members.some((snap) => snap.id === selectedId));\n if (index === -1) {\n return { position: 0, total: groups.length, prev: null, next: null };\n }\n return {\n position: index + 1,\n total: groups.length,\n prev: groups[index - 1]?.representative ?? null,\n next: groups[index + 1]?.representative ?? null,\n };\n}\n\n/** The group a snapshot belongs to. */\nexport function groupOf(snapshots: Snapshot[], snapshotId: string): SnapshotGroup | undefined {\n return groupSnapshots(snapshots).find((group) =>\n group.members.some((snap) => snap.id === snapshotId),\n );\n}\n\n/** Diff size as a percentage string (\"3.1%\"), or null when not computed. */\nexport function diffPercent(snapshot: Pick<Snapshot, \"diffRatio\">): string | null {\n return snapshot.diffRatio === null || snapshot.diffRatio === undefined\n ? null\n : `${(snapshot.diffRatio * 100).toFixed(1)}%`;\n}\n\n/** Review-page URL for one snapshot. */\nexport function snapshotPageUrl(project: Project, build: Build, snapshotId: string): string {\n return `/projects/${project.slug}/builds/${build.id}/diff?snapshot=${snapshotId}`;\n}\n\n/** API URL of a snapshot image (`image`), its diff overlay, or its baseline. */\nexport function snapshotImageUrl(\n project: Project,\n build: Build,\n snapshot: Snapshot,\n kind: \"image\" | \"diff\" | \"baseline\",\n): string {\n return `/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snapshot.id}/${kind}`;\n}\n\n/** Mode and engine of a snapshot for labels: `dark`, `dark · firefox` (chromium is implicit). */\nexport function snapshotVariant(snap: Pick<Snapshot, \"viewportName\" | \"browser\">): string {\n const browser = snap.browser ?? \"chromium\";\n return browser === \"chromium\" ? snap.viewportName : `${snap.viewportName} · ${browser}`;\n}\n\n/** A cached design render the review stage can compare the screenshot with. */\nexport interface DesignSource {\n /** Stage key: `design` for a lone render, `design-<n>` when the component links several. */\n value: string;\n /** Source switch label. */\n label: string;\n /** API URL of the cached render. */\n src: string;\n name: string | null;\n providerName: string;\n}\n\n/**\n * The design links of a component that have a cached render, as comparison sources.\n * A link without a render is not offered (the design strip still lists it).\n */\nexport function designSources(items: readonly DesignItem[]): DesignSource[] {\n const rendered = items.flatMap((item) =>\n item.renderUrl === null ? [] : [{ item, src: item.renderUrl }],\n );\n const several = rendered.length > 1;\n return rendered.map(({ item, src }, index) => ({\n value: several ? `design-${index + 1}` : \"design\",\n label: several ? `${item.providerName} ${index + 1}` : \"Design\",\n src,\n name: item.name,\n providerName: item.providerName,\n }));\n}\n"],"mappings":"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"}
|
|
@@ -5,10 +5,11 @@ import { css } from "../ui/css.mjs";
|
|
|
5
5
|
import { statusTone } from "../ui/feedback.mjs";
|
|
6
6
|
import { Segmented } from "../ui/segmented.mjs";
|
|
7
7
|
import { Thumbnail } from "../ui/thumbnail.mjs";
|
|
8
|
+
import { a11yCarrierOf, a11yFindingsOf } from "./a11y-violations.mjs";
|
|
9
|
+
import { diffPercent, groupSnapshots, isOpenGroup, reviewProgress, snapshotImageUrl, snapshotPageUrl } from "./build-diff-model.mjs";
|
|
8
10
|
import { filmBody, filmItem, filmItemActive, filmStatus, filmSub, filmTitle, filmstrip, filmstripHead, filmstripList, statusDots } from "../ui/styles/review-layout.mjs";
|
|
9
|
-
import { diffPercent, reviewProgress, snapshotImageUrl, snapshotPageUrl } from "./build-diff-model.mjs";
|
|
10
11
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
12
|
+
// src/pages/build-diff-nav.tsx
|
|
12
13
|
/**
|
|
13
14
|
* The list filters purely in CSS off `data-filter`, so it works before the
|
|
14
15
|
* review script runs. The selected snapshot always stays visible.
|
|
@@ -20,6 +21,9 @@ const filteredList = css`
|
|
|
20
21
|
[data-status]:not([data-status="new"]):not([data-status="changed"]):not([aria-current="true"]) {
|
|
21
22
|
display: none;
|
|
22
23
|
}
|
|
24
|
+
&[data-filter="a11y"] [data-snapshot-link]:not([data-a11y]):not([aria-current="true"]) {
|
|
25
|
+
display: none;
|
|
26
|
+
}
|
|
23
27
|
&[data-filter="done"]
|
|
24
28
|
[data-status]:not([data-status="approved"]):not([data-status="rejected"]):not(
|
|
25
29
|
[aria-current="true"]
|
|
@@ -28,15 +32,20 @@ const filteredList = css`
|
|
|
28
32
|
}
|
|
29
33
|
`;
|
|
30
34
|
function FilmItem(props) {
|
|
31
|
-
const { project, build,
|
|
35
|
+
const { project, build, group, active } = props;
|
|
36
|
+
const snap = group.representative;
|
|
32
37
|
const url = snapshotPageUrl(project, build, snap.id);
|
|
33
38
|
const percent = diffPercent(snap);
|
|
39
|
+
const carrier = a11yCarrierOf(snap, group.members);
|
|
40
|
+
const a11yCount = a11yFindingsOf(carrier).length;
|
|
41
|
+
const tags = [group.modeKey === "desktop" ? "" : group.modeKey, group.members.length > 1 ? `${group.members.length} browsers` : ""].filter((tag) => tag !== "");
|
|
34
42
|
return /* @__PURE__ */ jsxs("a", {
|
|
35
43
|
href: url,
|
|
36
44
|
class: active ? filmItemActive : filmItem,
|
|
37
45
|
"data-snapshot-link": true,
|
|
38
46
|
"data-snapshot-id": snap.id,
|
|
39
|
-
"data-status": snap.status,
|
|
47
|
+
"data-status": isOpenGroup(group) ? "changed" : snap.status,
|
|
48
|
+
"data-a11y": a11yCount > 0 ? "" : void 0,
|
|
40
49
|
"aria-current": active ? "true" : void 0,
|
|
41
50
|
"hx-get": url,
|
|
42
51
|
"hx-target": "body",
|
|
@@ -55,7 +64,7 @@ function FilmItem(props) {
|
|
|
55
64
|
}),
|
|
56
65
|
/* @__PURE__ */ jsx("span", {
|
|
57
66
|
class: filmSub,
|
|
58
|
-
children: snap.storyTitle
|
|
67
|
+
children: [snap.storyTitle, ...tags].join(" · ")
|
|
59
68
|
}),
|
|
60
69
|
/* @__PURE__ */ jsxs("span", {
|
|
61
70
|
class: filmStatus,
|
|
@@ -65,18 +74,21 @@ function FilmItem(props) {
|
|
|
65
74
|
"aria-hidden": "true"
|
|
66
75
|
}),
|
|
67
76
|
snap.status,
|
|
68
|
-
percent ? ` · ${percent}` : ""
|
|
77
|
+
percent ? ` · ${percent}` : "",
|
|
78
|
+
a11yCount > 0 ? ` · a11y ${a11yCount}${carrier.browser ? ` (${carrier.browser})` : ""}` : ""
|
|
69
79
|
]
|
|
70
80
|
})
|
|
71
81
|
]
|
|
72
82
|
})]
|
|
73
83
|
});
|
|
74
84
|
}
|
|
75
|
-
/** Left filmstrip: filter chips plus a thumbnail per
|
|
85
|
+
/** Left filmstrip: filter chips plus a thumbnail per story/mode (browsers share one entry). */
|
|
76
86
|
function DiffNav(props) {
|
|
77
87
|
const { project, build, snapshots, selectedId } = props;
|
|
88
|
+
const groups = groupSnapshots(snapshots);
|
|
78
89
|
const progress = reviewProgress(snapshots);
|
|
79
90
|
const filter = progress.pending > 0 ? "review" : "all";
|
|
91
|
+
const withA11y = groups.filter((group) => a11yFindingsOf(a11yCarrierOf(group.representative, group.members)).length > 0).length;
|
|
80
92
|
return /* @__PURE__ */ jsxs("aside", {
|
|
81
93
|
class: filmstrip,
|
|
82
94
|
"aria-label": "Snapshots",
|
|
@@ -92,14 +104,18 @@ function DiffNav(props) {
|
|
|
92
104
|
active: filter === "review"
|
|
93
105
|
},
|
|
94
106
|
{
|
|
95
|
-
label: `All ${
|
|
107
|
+
label: `All ${groups.length}`,
|
|
96
108
|
value: "all",
|
|
97
109
|
active: filter === "all"
|
|
98
110
|
},
|
|
99
111
|
{
|
|
100
112
|
label: `Done ${progress.done}`,
|
|
101
113
|
value: "done"
|
|
102
|
-
}
|
|
114
|
+
},
|
|
115
|
+
...withA11y > 0 ? [{
|
|
116
|
+
label: `A11y ${withA11y}`,
|
|
117
|
+
value: "a11y"
|
|
118
|
+
}] : []
|
|
103
119
|
]
|
|
104
120
|
})
|
|
105
121
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -107,16 +123,15 @@ function DiffNav(props) {
|
|
|
107
123
|
"data-current": selectedId,
|
|
108
124
|
"data-filter": filter,
|
|
109
125
|
class: filteredList,
|
|
110
|
-
children:
|
|
126
|
+
children: groups.map((group) => /* @__PURE__ */ jsx(FilmItem, {
|
|
111
127
|
project,
|
|
112
128
|
build,
|
|
113
|
-
|
|
114
|
-
active:
|
|
115
|
-
},
|
|
129
|
+
group,
|
|
130
|
+
active: group.members.some((snap) => snap.id === selectedId)
|
|
131
|
+
}, group.key))
|
|
116
132
|
})]
|
|
117
133
|
});
|
|
118
134
|
}
|
|
119
|
-
//#endregion
|
|
120
135
|
export { DiffNav };
|
|
121
136
|
|
|
122
137
|
//# sourceMappingURL=build-diff-nav.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-diff-nav.mjs","names":[],"sources":["../../src/pages/build-diff-nav.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Segmented, Thumbnail, statusTone } from \"../ui/components.tsx\";\nimport { css } from \"../ui/css.ts\";\nimport {\n filmBody,\n filmItem,\n filmItemActive,\n filmStatus,\n filmSub,\n filmTitle,\n filmstrip,\n filmstripHead,\n filmstripList,\n statusDots,\n} from \"../ui/styles/review-layout.ts\";\nimport {\n diffPercent,\n reviewProgress,\n snapshotImageUrl,\n snapshotPageUrl,\n} from \"./build-diff-model.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/**\n * The list filters purely in CSS off `data-filter`, so it works before the\n * review script runs. The selected snapshot always stays visible.\n */\nconst filteredList = css`\n /* filmstrip-filtered */\n ${filmstripList}\n &[data-filter=\"review\"]\n [data-status]:not([data-status=\"new\"]):not([data-status=\"changed\"]):not([aria-current=\"true\"]) {\n display: none;\n }\n &[data-filter=\"done\"]\n [data-status]:not([data-status=\"approved\"]):not([data-status=\"rejected\"]):not(\n [aria-current=\"true\"]\n ) {\n display: none;\n }\n`;\n\n/** Snapshot navigator props for the diff review page. */\nexport interface DiffNavProps {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n selectedId?: string;\n}\n\nfunction FilmItem(props: {\n project: Project;\n build: Build;\n
|
|
1
|
+
{"version":3,"file":"build-diff-nav.mjs","names":[],"sources":["../../src/pages/build-diff-nav.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Segmented, Thumbnail, statusTone } from \"../ui/components.tsx\";\nimport { css } from \"../ui/css.ts\";\nimport {\n filmBody,\n filmItem,\n filmItemActive,\n filmStatus,\n filmSub,\n filmTitle,\n filmstrip,\n filmstripHead,\n filmstripList,\n statusDots,\n} from \"../ui/styles/review-layout.ts\";\nimport { a11yCarrierOf, a11yFindingsOf } from \"./a11y-violations.tsx\";\nimport {\n diffPercent,\n groupSnapshots,\n isOpenGroup,\n reviewProgress,\n snapshotImageUrl,\n snapshotPageUrl,\n type SnapshotGroup,\n} from \"./build-diff-model.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/**\n * The list filters purely in CSS off `data-filter`, so it works before the\n * review script runs. The selected snapshot always stays visible.\n */\nconst filteredList = css`\n /* filmstrip-filtered */\n ${filmstripList}\n &[data-filter=\"review\"]\n [data-status]:not([data-status=\"new\"]):not([data-status=\"changed\"]):not([aria-current=\"true\"]) {\n display: none;\n }\n &[data-filter=\"a11y\"] [data-snapshot-link]:not([data-a11y]):not([aria-current=\"true\"]) {\n display: none;\n }\n &[data-filter=\"done\"]\n [data-status]:not([data-status=\"approved\"]):not([data-status=\"rejected\"]):not(\n [aria-current=\"true\"]\n ) {\n display: none;\n }\n`;\n\n/** Snapshot navigator props for the diff review page. */\nexport interface DiffNavProps {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n selectedId?: string;\n}\n\nfunction FilmItem(props: {\n project: Project;\n build: Build;\n group: SnapshotGroup;\n active: boolean;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, group, active } = props;\n const snap = group.representative;\n const url = snapshotPageUrl(project, build, snap.id);\n const percent = diffPercent(snap);\n const carrier = a11yCarrierOf(snap, group.members);\n const a11yCount = a11yFindingsOf(carrier).length;\n const tags = [\n group.modeKey === \"desktop\" ? \"\" : group.modeKey,\n group.members.length > 1 ? `${group.members.length} browsers` : \"\",\n ].filter((tag) => tag !== \"\");\n return (\n <a\n href={url}\n class={active ? filmItemActive : filmItem}\n data-snapshot-link\n data-snapshot-id={snap.id}\n data-status={isOpenGroup(group) ? \"changed\" : snap.status}\n data-a11y={a11yCount > 0 ? \"\" : undefined}\n aria-current={active ? \"true\" : undefined}\n hx-get={url}\n hx-target=\"body\"\n hx-push-url=\"true\"\n >\n <Thumbnail src={snapshotImageUrl(project, build, snap, \"image\")} alt=\"\" placeholder=\"…\" />\n <span class={filmBody}>\n <span class={filmTitle} title={`${snap.storyTitle} / ${snap.storyName}`}>\n {snap.storyName}\n </span>\n <span class={filmSub}>{[snap.storyTitle, ...tags].join(\" · \")}</span>\n <span class={filmStatus}>\n <span class={statusDots[statusTone(snap.status)]} aria-hidden=\"true\" />\n {snap.status}\n {percent ? ` · ${percent}` : \"\"}\n {a11yCount > 0\n ? ` · a11y ${a11yCount}${carrier.browser ? ` (${carrier.browser})` : \"\"}`\n : \"\"}\n </span>\n </span>\n </a>\n );\n}\n\n/** Left filmstrip: filter chips plus a thumbnail per story/mode (browsers share one entry). */\nexport function DiffNav(props: DiffNavProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, snapshots, selectedId } = props;\n const groups = groupSnapshots(snapshots);\n const progress = reviewProgress(snapshots);\n const filter = progress.pending > 0 ? \"review\" : \"all\";\n const withA11y = groups.filter(\n (group) => a11yFindingsOf(a11yCarrierOf(group.representative, group.members)).length > 0,\n ).length;\n return (\n <aside class={filmstrip} aria-label=\"Snapshots\">\n <div class={filmstripHead}>\n <Segmented\n label=\"Filter snapshots\"\n data-filter-switch\n items={[\n {\n label: `Needs review ${progress.pending}`,\n value: \"review\",\n active: filter === \"review\",\n },\n { label: `All ${groups.length}`, value: \"all\", active: filter === \"all\" },\n { label: `Done ${progress.done}`, value: \"done\" },\n ...(withA11y > 0 ? [{ label: `A11y ${withA11y}`, value: \"a11y\" }] : []),\n ]}\n />\n </div>\n <div data-diff-nav data-current={selectedId} data-filter={filter} class={filteredList}>\n {groups.map((group): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <FilmItem\n key={group.key}\n project={project}\n build={build}\n group={group}\n active={group.members.some((snap) => snap.id === selectedId)}\n />\n ))}\n </div>\n </aside>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,MAAM,eAAe,GAAG;;IAEpB,cAAc;;;;;;;;;;;;;;;AAwBlB,SAAS,SAAS,OAKiC;CACjD,MAAM,EAAE,SAAS,OAAO,OAAO,WAAW;CAC1C,MAAM,OAAO,MAAM;CACnB,MAAM,MAAM,gBAAgB,SAAS,OAAO,KAAK,EAAE;CACnD,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,UAAU,cAAc,MAAM,MAAM,OAAO;CACjD,MAAM,YAAY,eAAe,OAAO,CAAC,CAAC;CAC1C,MAAM,OAAO,CACX,MAAM,YAAY,YAAY,KAAK,MAAM,SACzC,MAAM,QAAQ,SAAS,IAAI,GAAG,MAAM,QAAQ,OAAO,aAAa,EAClE,CAAC,CAAC,QAAQ,QAAQ,QAAQ,EAAE;CAC5B,OACE,qBAAC,KAAD;EACE,MAAM;EACN,OAAO,SAAS,iBAAiB;EACjC,sBAAA;EACA,oBAAkB,KAAK;EACvB,eAAa,YAAY,KAAK,IAAI,YAAY,KAAK;EACnD,aAAW,YAAY,IAAI,KAAK,KAAA;EAChC,gBAAc,SAAS,SAAS,KAAA;EAChC,UAAQ;EACR,aAAU;EACV,eAAY;EAVd,UAAA,CAYE,oBAAC,WAAD;GAAW,KAAK,iBAAiB,SAAS,OAAO,MAAM,OAAO;GAAG,KAAI;GAAG,aAAY;EAAK,CAAA,GACzF,qBAAC,QAAD;GAAM,OAAO;GAAb,UAAA;IACE,oBAAC,QAAD;KAAM,OAAO;KAAW,OAAO,GAAG,KAAK,WAAW,KAAK,KAAK;KACzD,UAAA,KAAK;IACF,CAAA;IACN,oBAAC,QAAD;KAAM,OAAO;KAAU,UAAA,CAAC,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC,KAAK,KAAK;IAAQ,CAAA;IACpE,qBAAC,QAAD;KAAM,OAAO;KAAb,UAAA;MACE,oBAAC,QAAD;OAAM,OAAO,WAAW,WAAW,KAAK,MAAM;OAAI,eAAY;MAAQ,CAAA;MACrE,KAAK;MACL,UAAU,MAAM,YAAY;MAC5B,YAAY,IACT,WAAW,YAAY,QAAQ,UAAU,KAAK,QAAQ,QAAQ,KAAK,OACnE;KACA;;GACF;EACL,CAAA,CAAA;;AAEP;;AAGA,SAAgB,QAAQ,OAAqE;CAC3F,MAAM,EAAE,SAAS,OAAO,WAAW,eAAe;CAClD,MAAM,SAAS,eAAe,SAAS;CACvC,MAAM,WAAW,eAAe,SAAS;CACzC,MAAM,SAAS,SAAS,UAAU,IAAI,WAAW;CACjD,MAAM,WAAW,OAAO,QACrB,UAAU,eAAe,cAAc,MAAM,gBAAgB,MAAM,OAAO,CAAC,CAAC,CAAC,SAAS,CACzF,CAAC,CAAC;CACF,OACE,qBAAC,SAAD;EAAO,OAAO;EAAW,cAAW;EAApC,UAAA,CACE,oBAAC,OAAD;GAAK,OAAO;GACV,UAAA,oBAAC,WAAD;IACE,OAAM;IACN,sBAAA;IACA,OAAO;KACL;MACE,OAAO,gBAAgB,SAAS;MAChC,OAAO;MACP,QAAQ,WAAW;KACrB;KACA;MAAE,OAAO,OAAO,OAAO;MAAU,OAAO;MAAO,QAAQ,WAAW;KAAM;KACxE;MAAE,OAAO,QAAQ,SAAS;MAAQ,OAAO;KAAO;KAChD,GAAI,WAAW,IAAI,CAAC;MAAE,OAAO,QAAQ;MAAY,OAAO;KAAO,CAAC,IAAI,CAAC;IACvE;GACD,CAAA;EACE,CAAA,GACL,oBAAC,OAAD;GAAK,iBAAA;GAAc,gBAAc;GAAY,eAAa;GAAQ,OAAO;GACtE,UAAA,OAAO,KAAK,UACX,oBAAC,UAAD;IAEW;IACF;IACA;IACP,QAAQ,MAAM,QAAQ,MAAM,SAAS,KAAK,OAAO,UAAU;GAC5D,GALM,MAAM,GAKZ,CACF;EACE,CAAA,CACA;;AAEX"}
|