@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,62 +1,101 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
+
import { testingEnabled } from "../store.mjs";
|
|
4
5
|
import { PageHeader } from "../ui/layout.mjs";
|
|
5
6
|
import { SubNavLayout } from "../ui/subnav.mjs";
|
|
6
7
|
import { DocumentLayout } from "../ui/document.mjs";
|
|
8
|
+
import { renderSettingsAi } from "./settings-ai.mjs";
|
|
9
|
+
import { renderSettingsCapture } from "./settings-capture.mjs";
|
|
10
|
+
import { renderSettingsDesign } from "./settings-design.mjs";
|
|
7
11
|
import { renderSettingsGeneral } from "./settings-general.mjs";
|
|
8
12
|
import { renderSettingsLabels } from "./settings-labels.mjs";
|
|
13
|
+
import { renderSettingsLinks } from "./settings-links.mjs";
|
|
9
14
|
import { renderSettingsMembers } from "./settings-members.mjs";
|
|
10
15
|
import { renderSettingsNotifications } from "./settings-notifications.mjs";
|
|
11
|
-
import { renderSettingsStatus } from "./settings-status.mjs";
|
|
12
16
|
import { renderSettingsTests } from "./settings-tests.mjs";
|
|
13
17
|
import { renderSettingsTokens } from "./settings-tokens.mjs";
|
|
14
18
|
import { renderSettingsWebhooks } from "./settings-webhooks.mjs";
|
|
15
19
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
16
|
-
|
|
20
|
+
// src/pages/project-settings.tsx
|
|
17
21
|
function tabHref(project, tab) {
|
|
18
22
|
return tab === "general" ? `/projects/${project.slug}/settings` : `/projects/${project.slug}/settings/${tab}`;
|
|
19
23
|
}
|
|
20
24
|
const TAB_LABELS = {
|
|
21
25
|
general: "General",
|
|
26
|
+
capture: "Capture",
|
|
22
27
|
tests: "Tests",
|
|
23
28
|
labels: "Labels",
|
|
29
|
+
links: "Links",
|
|
30
|
+
design: "Design",
|
|
24
31
|
tokens: "Tokens",
|
|
25
32
|
webhooks: "Webhooks",
|
|
26
33
|
notifications: "Notifications",
|
|
27
34
|
members: "Members",
|
|
28
|
-
|
|
35
|
+
ai: "AI"
|
|
29
36
|
};
|
|
30
37
|
const TAB_ICONS = {
|
|
31
38
|
general: "settings",
|
|
39
|
+
capture: "image",
|
|
32
40
|
tests: "activity",
|
|
33
41
|
labels: "tag",
|
|
42
|
+
links: "external-link",
|
|
43
|
+
design: "image",
|
|
34
44
|
tokens: "key",
|
|
35
45
|
webhooks: "webhook",
|
|
36
46
|
notifications: "bell",
|
|
37
47
|
members: "users",
|
|
38
|
-
|
|
48
|
+
ai: "sparkles"
|
|
39
49
|
};
|
|
50
|
+
/** Tabs whose pages are `testing` routes: hidden on a hosting-only instance. */
|
|
51
|
+
const TESTING_TABS = /* @__PURE__ */ new Set([
|
|
52
|
+
"capture",
|
|
53
|
+
"tests",
|
|
54
|
+
"ai"
|
|
55
|
+
]);
|
|
40
56
|
const SETTINGS_TABS = [
|
|
41
57
|
"general",
|
|
58
|
+
"capture",
|
|
42
59
|
"tests",
|
|
43
60
|
"labels",
|
|
61
|
+
"links",
|
|
62
|
+
"design",
|
|
44
63
|
"tokens",
|
|
45
64
|
"webhooks",
|
|
46
65
|
"notifications",
|
|
47
66
|
"members",
|
|
48
|
-
"
|
|
67
|
+
"ai"
|
|
49
68
|
];
|
|
69
|
+
/** A tab shows when its page exists: `testing` tabs need testing, and the AI tab needs AI. */
|
|
70
|
+
function settingsTabShown(tab, data) {
|
|
71
|
+
if (TESTING_TABS.has(tab) && !testingEnabled()) return false;
|
|
72
|
+
return tab !== "ai" || data.ai?.available === true;
|
|
73
|
+
}
|
|
74
|
+
/** Project-behaviour tabs (general, tests, AI); undefined for any other tab. */
|
|
75
|
+
function renderBehaviourTab(data, formState) {
|
|
76
|
+
const { project, activeTab } = data;
|
|
77
|
+
if (activeTab === "general") return renderSettingsGeneral(project, formState, data.isAdmin, data.targets);
|
|
78
|
+
if (activeTab === "capture") return renderSettingsCapture(project, data.isAdmin, formState, data.targets);
|
|
79
|
+
if (activeTab === "ai") return renderSettingsAi(project, data.ai, formState);
|
|
80
|
+
return activeTab === "tests" ? renderSettingsTests(project, data.isAdmin, formState) : void 0;
|
|
81
|
+
}
|
|
82
|
+
/** Labels, links, tokens, and webhooks tabs; undefined for any other tab. */
|
|
83
|
+
function renderCatalogTab(data, formState) {
|
|
84
|
+
const { project, activeTab, isAdmin } = data;
|
|
85
|
+
if (activeTab === "labels") return renderSettingsLabels(project, data.labelTypes, isAdmin, formState);
|
|
86
|
+
if (activeTab === "links") return renderSettingsLinks(project, data.links, data.linkCandidates, isAdmin, formState);
|
|
87
|
+
if (activeTab === "design") return renderSettingsDesign(project, data.design, isAdmin, formState);
|
|
88
|
+
if (activeTab === "tokens") return renderSettingsTokens(project, data.tokens, isAdmin, formState);
|
|
89
|
+
return activeTab === "webhooks" ? renderSettingsWebhooks(project, data.webhooks, isAdmin, formState) : void 0;
|
|
90
|
+
}
|
|
50
91
|
function renderActiveTab(data, formState) {
|
|
51
92
|
const { project, activeTab } = data;
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
if (activeTab === "
|
|
57
|
-
if (activeTab === "notifications") return renderSettingsNotifications(project, data.notificationChannels, data.notifyProviders, data.mySubscription, data.isAdmin, formState);
|
|
93
|
+
const behaviour = renderBehaviourTab(data, formState);
|
|
94
|
+
if (behaviour !== void 0) return behaviour;
|
|
95
|
+
const catalog = renderCatalogTab(data, formState);
|
|
96
|
+
if (catalog !== void 0) return catalog;
|
|
97
|
+
if (activeTab === "notifications") return renderSettingsNotifications(project, data.notificationChannels, data.notifyProviders, data.mySubscription, data.isAdmin, formState, data.notificationsPaused);
|
|
58
98
|
if (activeTab === "members") return renderSettingsMembers(project, data.members, data.groupMappings, data.isAdmin, formState);
|
|
59
|
-
if (activeTab === "status") return renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin, formState);
|
|
60
99
|
return null;
|
|
61
100
|
}
|
|
62
101
|
/** Project settings shell: tab navigation plus the active settings tab. */
|
|
@@ -79,7 +118,7 @@ function renderProjectSettingsPage(data, formState) {
|
|
|
79
118
|
] })
|
|
80
119
|
}), /* @__PURE__ */ jsx(SubNavLayout, {
|
|
81
120
|
label: "Settings sections",
|
|
82
|
-
items: SETTINGS_TABS.map((tab) => ({
|
|
121
|
+
items: SETTINGS_TABS.filter((tab) => settingsTabShown(tab, data)).map((tab) => ({
|
|
83
122
|
label: TAB_LABELS[tab],
|
|
84
123
|
href: tabHref(project, tab),
|
|
85
124
|
icon: TAB_ICONS[tab],
|
|
@@ -89,7 +128,6 @@ function renderProjectSettingsPage(data, formState) {
|
|
|
89
128
|
})]
|
|
90
129
|
});
|
|
91
130
|
}
|
|
92
|
-
//#endregion
|
|
93
131
|
export { renderProjectSettingsPage };
|
|
94
132
|
|
|
95
133
|
//# sourceMappingURL=project-settings.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-settings.mjs","names":[],"sources":["../../src/pages/project-settings.tsx"],"sourcesContent":["import type { GitHostProvider } from \"@storyshelf/core/adapter/git-host\";\nimport type { LabelType } from \"@storyshelf/core/schema\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport type { ProjectGroupMapping } from \"@storyshelf/core/schema\";\nimport type { Token } from \"@storyshelf/core/schema\";\nimport { PageHeader, SubNavLayout } from \"../ui/components.tsx\";\nimport type { IconName } from \"../ui/components.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { renderSettingsGeneral } from \"./settings-general.tsx\";\nimport { renderSettingsLabels } from \"./settings-labels.tsx\";\nimport { renderSettingsMembers, type SettingsMember } from \"./settings-members.tsx\";\nimport {\n renderSettingsNotifications,\n type SettingsMySubscription,\n type SettingsNotificationChannel,\n} from \"./settings-notifications.tsx\";\nimport { renderSettingsStatus, type SettingsStatusConfig } from \"./settings-status.tsx\";\nimport { renderSettingsTests } from \"./settings-tests.tsx\";\nimport { renderSettingsTokens } from \"./settings-tokens.tsx\";\nimport { renderSettingsWebhooks, type SettingsWebhook } from \"./settings-webhooks.tsx\";\n\n/** Tabs available in the project settings section. */\nexport type SettingsTab =\n | \"general\"\n | \"tests\"\n | \"labels\"\n | \"tokens\"\n | \"webhooks\"\n | \"notifications\"\n | \"members\"\n | \"status\";\n\n/** Data required to render the project settings page with its active tab. */\nexport interface ProjectSettingsData {\n project: Project;\n activeTab: SettingsTab;\n labelTypes: LabelType[];\n tokens: Omit<Token, \"hash\">[];\n members: SettingsMember[];\n groupMappings: ProjectGroupMapping[];\n webhooks: SettingsWebhook[];\n notificationChannels: SettingsNotificationChannel[];\n notifyProviders: string[];\n mySubscription: SettingsMySubscription | null;\n statusConfigs: SettingsStatusConfig[];\n gitHosts: GitHostProvider[];\n isAdmin: boolean;\n}\n\n/** Form state shared by the settings tabs (field errors, global error, one-time secret). */\nexport interface SettingsFormState {\n errors?: Record<string, string>;\n globalError?: string;\n secret?: string;\n}\n\nfunction tabHref(project: Project, tab: SettingsTab): string {\n return tab === \"general\"\n ? `/projects/${project.slug}/settings`\n : `/projects/${project.slug}/settings/${tab}`;\n}\n\nconst TAB_LABELS: Record<SettingsTab, string> = {\n general: \"General\",\n tests: \"Tests\",\n labels: \"Labels\",\n tokens: \"Tokens\",\n webhooks: \"Webhooks\",\n notifications: \"Notifications\",\n members: \"Members\",\n status: \"Git status\",\n};\n\nconst TAB_ICONS: Record<SettingsTab, IconName> = {\n general: \"settings\",\n tests: \"activity\",\n labels: \"tag\",\n tokens: \"key\",\n webhooks: \"webhook\",\n notifications: \"bell\",\n members: \"users\",\n status: \"git-branch\",\n};\n\nconst SETTINGS_TABS: SettingsTab[] = [\n \"general\",\n \"tests\",\n \"labels\",\n \"tokens\",\n \"webhooks\",\n \"notifications\",\n \"members\",\n \"status\",\n];\n\nfunction renderActiveTab(data: ProjectSettingsData, formState?: SettingsFormState): unknown {\n const { project, activeTab } = data;\n if (activeTab === \"general\") return renderSettingsGeneral(project, formState, data.isAdmin);\n if (activeTab === \"tests\") return renderSettingsTests(project, data.isAdmin, formState);\n if (activeTab === \"labels\")\n return renderSettingsLabels(project, data.labelTypes, data.isAdmin, formState);\n if (activeTab === \"tokens\")\n return renderSettingsTokens(project, data.tokens, data.isAdmin, formState);\n if (activeTab === \"webhooks\")\n return renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState);\n if (activeTab === \"notifications\")\n return renderSettingsNotifications(\n project,\n data.notificationChannels,\n data.notifyProviders,\n data.mySubscription,\n data.isAdmin,\n formState,\n );\n if (activeTab === \"members\")\n return renderSettingsMembers(\n project,\n data.members,\n data.groupMappings,\n data.isAdmin,\n formState,\n );\n if (activeTab === \"status\")\n return renderSettingsStatus(\n project,\n data.statusConfigs,\n data.gitHosts,\n data.isAdmin,\n formState,\n );\n return null;\n}\n\n/** Project settings shell: tab navigation plus the active settings tab. */\nexport function renderProjectSettingsPage(\n data: ProjectSettingsData,\n formState?: SettingsFormState,\n): RenderedContent {\n const { project, activeTab } = data;\n return (\n <DocumentLayout\n title={`${project.name} · Settings`}\n nav={{\n active: \"settings\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: activeTab === \"general\" ? [] : [{ label: TAB_LABELS[activeTab] }],\n }}\n >\n <PageHeader\n title=\"Project settings\"\n description={\n <>Manage general settings, labels, tokens, webhooks and members for {project.name}.</>\n }\n />\n\n <SubNavLayout\n label=\"Settings sections\"\n items={SETTINGS_TABS.map((tab) => ({\n label: TAB_LABELS[tab],\n href: tabHref(project, tab),\n icon: TAB_ICONS[tab],\n active: activeTab === tab,\n }))}\n >\n {renderActiveTab(data, formState)}\n </SubNavLayout>\n </DocumentLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAwDA,SAAS,QAAQ,SAAkB,KAA0B;CAC3D,OAAO,QAAQ,YACX,aAAa,QAAQ,KAAK,aAC1B,aAAa,QAAQ,KAAK,YAAY;AAC5C;AAEA,MAAM,aAA0C;CAC9C,SAAS;CACT,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,eAAe;CACf,SAAS;CACT,QAAQ;AACV;AAEA,MAAM,YAA2C;CAC/C,SAAS;CACT,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,eAAe;CACf,SAAS;CACT,QAAQ;AACV;AAEA,MAAM,gBAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,gBAAgB,MAA2B,WAAwC;CAC1F,MAAM,EAAE,SAAS,cAAc;CAC/B,IAAI,cAAc,WAAW,OAAO,sBAAsB,SAAS,WAAW,KAAK,OAAO;CAC1F,IAAI,cAAc,SAAS,OAAO,oBAAoB,SAAS,KAAK,SAAS,SAAS;CACtF,IAAI,cAAc,UAChB,OAAO,qBAAqB,SAAS,KAAK,YAAY,KAAK,SAAS,SAAS;CAC/E,IAAI,cAAc,UAChB,OAAO,qBAAqB,SAAS,KAAK,QAAQ,KAAK,SAAS,SAAS;CAC3E,IAAI,cAAc,YAChB,OAAO,uBAAuB,SAAS,KAAK,UAAU,KAAK,SAAS,SAAS;CAC/E,IAAI,cAAc,iBAChB,OAAO,4BACL,SACA,KAAK,sBACL,KAAK,iBACL,KAAK,gBACL,KAAK,SACL,SACF;CACF,IAAI,cAAc,WAChB,OAAO,sBACL,SACA,KAAK,SACL,KAAK,eACL,KAAK,SACL,SACF;CACF,IAAI,cAAc,UAChB,OAAO,qBACL,SACA,KAAK,eACL,KAAK,UACL,KAAK,SACL,SACF;CACF,OAAO;AACT;;AAGA,SAAgB,0BACd,MACA,WACiB;CACjB,MAAM,EAAE,SAAS,cAAc;CAC/B,OACE,qBAAC,gBAAD;EACE,OAAO,GAAG,QAAQ,KAAK;EACvB,KAAK;GACH,QAAQ;GACR,aAAa,QAAQ;GACrB,aAAa,QAAQ;GACrB,OAAO,cAAc,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,WAAW,WAAW,CAAC;EACzE;EAPF,UAAA,CASE,oBAAC,YAAD;GACE,OAAM;GACN,aACE,qBAAA,UAAA,EAAA,UAAA;IAAE;IAAmE,QAAQ;IAAK;GAAG,EAAA,CAAA;EAExF,CAAA,GAED,oBAAC,cAAD;GACE,OAAM;GACN,OAAO,cAAc,KAAK,SAAS;IACjC,OAAO,WAAW;IAClB,MAAM,QAAQ,SAAS,GAAG;IAC1B,MAAM,UAAU;IAChB,QAAQ,cAAc;GACxB,EAAE;GAED,UAAA,gBAAgB,MAAM,SAAS;EACpB,CAAA,CACA;;AAEpB"}
|
|
1
|
+
{"version":3,"file":"project-settings.mjs","names":[],"sources":["../../src/pages/project-settings.tsx"],"sourcesContent":["import type { RunnerTarget } from \"@storyshelf/core/adapter/capture-runner\";\nimport type { LabelType } from \"@storyshelf/core/schema\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport type { ProjectGroupMapping } from \"@storyshelf/core/schema\";\nimport type { Token } from \"@storyshelf/core/schema\";\nimport { testingEnabled } from \"../store.ts\";\nimport { PageHeader, SubNavLayout } from \"../ui/components.tsx\";\nimport type { IconName } from \"../ui/components.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { renderSettingsAi, type SettingsAiData } from \"./settings-ai.tsx\";\nimport { renderSettingsCapture } from \"./settings-capture.tsx\";\nimport { renderSettingsDesign, type SettingsDesignData } from \"./settings-design.tsx\";\nimport { renderSettingsGeneral } from \"./settings-general.tsx\";\nimport { renderSettingsLabels } from \"./settings-labels.tsx\";\nimport {\n renderSettingsLinks,\n type SettingsLink,\n type SettingsLinkCandidate,\n} from \"./settings-links.tsx\";\nimport { renderSettingsMembers, type SettingsMember } from \"./settings-members.tsx\";\nimport {\n renderSettingsNotifications,\n type SettingsMySubscription,\n type SettingsNotificationChannel,\n type SettingsNotifyProvider,\n} from \"./settings-notifications.tsx\";\nimport { renderSettingsTests } from \"./settings-tests.tsx\";\nimport { renderSettingsTokens } from \"./settings-tokens.tsx\";\nimport { renderSettingsWebhooks, type SettingsWebhook } from \"./settings-webhooks.tsx\";\n\n/** Tabs available in the project settings section. */\nexport type SettingsTab =\n | \"general\"\n | \"capture\"\n | \"tests\"\n | \"labels\"\n | \"links\"\n | \"design\"\n | \"tokens\"\n | \"webhooks\"\n | \"notifications\"\n | \"members\"\n | \"ai\";\n\n/** Data required to render the project settings page with its active tab. */\nexport interface ProjectSettingsData {\n project: Project;\n activeTab: SettingsTab;\n labelTypes: LabelType[];\n links: SettingsLink[];\n linkCandidates: SettingsLinkCandidate[];\n design: SettingsDesignData;\n tokens: Omit<Token, \"hash\">[];\n members: SettingsMember[];\n groupMappings: ProjectGroupMapping[];\n webhooks: SettingsWebhook[];\n notificationChannels: SettingsNotificationChannel[];\n notifyProviders: SettingsNotifyProvider[];\n mySubscription: SettingsMySubscription | null;\n isAdmin: boolean;\n /** Notifiers are wired but no public origin resolves (ADR 0014, decision 11). */\n notificationsPaused?: boolean | undefined;\n /** AI availability and the profile choices (omitted ⇒ AI off). */\n ai?: SettingsAiData;\n /** What the capture runner offers (ADR 0007); omitted when it publishes no list. */\n targets?: readonly RunnerTarget[] | undefined;\n}\n\n/** Form state shared by the settings tabs (field errors, global error, one-time secret). */\nexport interface SettingsFormState {\n errors?: Record<string, string>;\n globalError?: string;\n secret?: string;\n /** Label type key whose edit form is open (labels tab). */\n editKey?: string;\n}\n\nfunction tabHref(project: Project, tab: SettingsTab): string {\n return tab === \"general\"\n ? `/projects/${project.slug}/settings`\n : `/projects/${project.slug}/settings/${tab}`;\n}\n\nconst TAB_LABELS: Record<SettingsTab, string> = {\n general: \"General\",\n capture: \"Capture\",\n tests: \"Tests\",\n labels: \"Labels\",\n links: \"Links\",\n design: \"Design\",\n tokens: \"Tokens\",\n webhooks: \"Webhooks\",\n notifications: \"Notifications\",\n members: \"Members\",\n ai: \"AI\",\n};\n\nconst TAB_ICONS: Record<SettingsTab, IconName> = {\n general: \"settings\",\n capture: \"image\",\n tests: \"activity\",\n labels: \"tag\",\n links: \"external-link\",\n design: \"image\",\n tokens: \"key\",\n webhooks: \"webhook\",\n notifications: \"bell\",\n members: \"users\",\n ai: \"sparkles\",\n};\n\n/** Tabs whose pages are `testing` routes: hidden on a hosting-only instance. */\nconst TESTING_TABS: ReadonlySet<SettingsTab> = new Set([\"capture\", \"tests\", \"ai\"]);\n\nconst SETTINGS_TABS: SettingsTab[] = [\n \"general\",\n \"capture\",\n \"tests\",\n \"labels\",\n \"links\",\n \"design\",\n \"tokens\",\n \"webhooks\",\n \"notifications\",\n \"members\",\n \"ai\",\n];\n\n/** A tab shows when its page exists: `testing` tabs need testing, and the AI tab needs AI. */\nfunction settingsTabShown(tab: SettingsTab, data: ProjectSettingsData): boolean {\n if (TESTING_TABS.has(tab) && !testingEnabled()) {\n return false;\n }\n return tab !== \"ai\" || data.ai?.available === true;\n}\n\n/** Project-behaviour tabs (general, tests, AI); undefined for any other tab. */\nfunction renderBehaviourTab(data: ProjectSettingsData, formState?: SettingsFormState): unknown {\n const { project, activeTab } = data;\n if (activeTab === \"general\")\n return renderSettingsGeneral(project, formState, data.isAdmin, data.targets);\n if (activeTab === \"capture\")\n return renderSettingsCapture(project, data.isAdmin, formState, data.targets);\n if (activeTab === \"ai\") return renderSettingsAi(project, data.ai, formState);\n return activeTab === \"tests\" ? renderSettingsTests(project, data.isAdmin, formState) : undefined;\n}\n\n/** Labels, links, tokens, and webhooks tabs; undefined for any other tab. */\nfunction renderCatalogTab(data: ProjectSettingsData, formState?: SettingsFormState): unknown {\n const { project, activeTab, isAdmin } = data;\n if (activeTab === \"labels\")\n return renderSettingsLabels(project, data.labelTypes, isAdmin, formState);\n if (activeTab === \"links\")\n return renderSettingsLinks(project, data.links, data.linkCandidates, isAdmin, formState);\n if (activeTab === \"design\") return renderSettingsDesign(project, data.design, isAdmin, formState);\n if (activeTab === \"tokens\") return renderSettingsTokens(project, data.tokens, isAdmin, formState);\n return activeTab === \"webhooks\"\n ? renderSettingsWebhooks(project, data.webhooks, isAdmin, formState)\n : undefined;\n}\n\nfunction renderActiveTab(data: ProjectSettingsData, formState?: SettingsFormState): unknown {\n const { project, activeTab } = data;\n const behaviour = renderBehaviourTab(data, formState);\n if (behaviour !== undefined) return behaviour;\n const catalog = renderCatalogTab(data, formState);\n if (catalog !== undefined) return catalog;\n if (activeTab === \"notifications\")\n return renderSettingsNotifications(\n project,\n data.notificationChannels,\n data.notifyProviders,\n data.mySubscription,\n data.isAdmin,\n formState,\n data.notificationsPaused,\n );\n if (activeTab === \"members\")\n return renderSettingsMembers(\n project,\n data.members,\n data.groupMappings,\n data.isAdmin,\n formState,\n );\n return null;\n}\n\n/** Project settings shell: tab navigation plus the active settings tab. */\nexport function renderProjectSettingsPage(\n data: ProjectSettingsData,\n formState?: SettingsFormState,\n): RenderedContent {\n const { project, activeTab } = data;\n return (\n <DocumentLayout\n title={`${project.name} · Settings`}\n nav={{\n active: \"settings\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: activeTab === \"general\" ? [] : [{ label: TAB_LABELS[activeTab] }],\n }}\n >\n <PageHeader\n title=\"Project settings\"\n description={\n <>Manage general settings, labels, tokens, webhooks and members for {project.name}.</>\n }\n />\n\n <SubNavLayout\n label=\"Settings sections\"\n items={SETTINGS_TABS.filter((tab) => settingsTabShown(tab, data)).map((tab) => ({\n label: TAB_LABELS[tab],\n href: tabHref(project, tab),\n icon: TAB_ICONS[tab],\n active: activeTab === tab,\n }))}\n >\n {renderActiveTab(data, formState)}\n </SubNavLayout>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -7,8 +7,9 @@ import { Badge, Meta, statusTone } from "../ui/feedback.mjs";
|
|
|
7
7
|
import { Card } from "../ui/layout.mjs";
|
|
8
8
|
import { Thumbnail } from "../ui/thumbnail.mjs";
|
|
9
9
|
import { RelativeTime } from "../ui/time.mjs";
|
|
10
|
+
import { healthTone } from "./health-panel.mjs";
|
|
10
11
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
12
|
+
// src/pages/projects-card.tsx
|
|
12
13
|
const thumbStrip = css`
|
|
13
14
|
/* project-thumbs */
|
|
14
15
|
display: grid;
|
|
@@ -156,7 +157,19 @@ function ProjectCard(props) {
|
|
|
156
157
|
children: project.gitDefaultBranch
|
|
157
158
|
})
|
|
158
159
|
] }),
|
|
159
|
-
/* @__PURE__ */ jsx(LatestBuild, { latest })
|
|
160
|
+
/* @__PURE__ */ jsx(LatestBuild, { latest }),
|
|
161
|
+
summary.health ? /* @__PURE__ */ jsx("a", {
|
|
162
|
+
href: urls.library,
|
|
163
|
+
children: /* @__PURE__ */ jsxs(Badge, {
|
|
164
|
+
tone: healthTone(summary.health.verdict),
|
|
165
|
+
icon: true,
|
|
166
|
+
children: [
|
|
167
|
+
"health: ",
|
|
168
|
+
summary.health.verdict,
|
|
169
|
+
summary.health.score === null ? "" : ` · ${Math.round(summary.health.score)}`
|
|
170
|
+
]
|
|
171
|
+
})
|
|
172
|
+
}) : null
|
|
160
173
|
]
|
|
161
174
|
}),
|
|
162
175
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -188,7 +201,6 @@ function ProjectCard(props) {
|
|
|
188
201
|
]
|
|
189
202
|
});
|
|
190
203
|
}
|
|
191
|
-
//#endregion
|
|
192
204
|
export { ProjectCard };
|
|
193
205
|
|
|
194
206
|
//# sourceMappingURL=projects-card.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects-card.mjs","names":[],"sources":["../../src/pages/projects-card.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport {\n Badge,\n Button,\n Card,\n Meta,\n RelativeTime,\n Thumbnail,\n statusTone,\n} from \"../ui/components.tsx\";\nimport { css } from \"../ui/css.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\nconst thumbStrip = css`\n /* project-thumbs */\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: var(--space-2);\n padding: var(--space-3);\n background: var(--accent-wash);\n border-bottom: 1px solid var(--border-subtle);\n`;\n\nconst cardBody = css`\n /* project-body */\n flex: 1;\n display: grid;\n align-content: start;\n gap: var(--space-2);\n padding: var(--space-4);\n`;\n\nconst cardTitleRow = css`\n /* project-title-row */\n display: flex;\n gap: var(--space-2);\n align-items: flex-start;\n justify-content: space-between;\n`;\n\nconst cardTitle = css`\n /* project-title */\n margin: 0;\n font-size: var(--text-lg);\n font-weight: 650;\n letter-spacing: -0.01em;\n & a {\n color: var(--text-primary);\n }\n & a:hover {\n color: var(--accent-fg);\n }\n`;\n\nconst latestRow = css`\n /* project-latest */\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n align-items: center;\n color: var(--text-secondary);\n font-size: var(--text-sm);\n`;\n\nconst cardFooter = css`\n /* project-footer */\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n padding: var(--space-3) var(--space-4);\n border-top: 1px solid var(--border-subtle);\n`;\n\n/** What the overview shows about a project's most recent build. */\nexport interface ProjectSummary {\n project: Project;\n latest: Build | null;\n /** Up to three screenshots of the latest build. */\n previews: Snapshot[];\n /** Snapshots in the latest build still waiting for a decision. */\n pending: number;\n}\n\nfunction PendingBadge({\n pending,\n href,\n}: {\n pending: number;\n href: string;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n if (pending === 0) {\n return (\n <Badge tone=\"success\" icon>\n All clear\n </Badge>\n );\n }\n return (\n <a href={href}>\n <Badge tone=\"warning\" icon>\n {pending} to review\n </Badge>\n </a>\n );\n}\n\nfunction LatestBuild({\n latest,\n}: {\n latest: Build | null;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n if (!latest) {\n return <Meta>No builds yet — upload one from CI.</Meta>;\n }\n return (\n <div class={latestRow}>\n <Meta as=\"span\" mono>\n {latest.gitBranch} · {latest.gitSha.slice(0, 7)}\n </Meta>\n <Badge tone={statusTone(latest.status)}>{latest.status}</Badge>\n <RelativeTime value={latest.createdAt} />\n </div>\n );\n}\n\nfunction Previews({\n project,\n latest,\n previews,\n}: Pick<ProjectSummary, \"project\" | \"latest\" | \"previews\">):\n | HtmlEscapedString\n | Promise<HtmlEscapedString> {\n return (\n <div class={thumbStrip}>\n {[0, 1, 2].map((slot): HtmlEscapedString | Promise<HtmlEscapedString> => {\n const snap = previews[slot];\n return (\n <Thumbnail\n key={String(slot)}\n src={\n snap && latest\n ? `/api/v1/projects/${project.slug}/builds/${latest.id}/snapshots/${snap.id}/image`\n : null\n }\n alt={snap ? `${snap.storyTitle} / ${snap.storyName}` : \"\"}\n placeholder=\"\"\n />\n );\n })}\n </div>\n );\n}\n\n/** One project in the overview grid: previews, latest build, review state, actions. */\nexport function ProjectCard(props: {\n summary: ProjectSummary;\n urls: { library: string; settings: string; storybook: string; review: string };\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { summary, urls } = props;\n const { project, latest, previews, pending } = summary;\n return (\n <Card\n fill\n data-filter-item\n data-filter-text={`${project.name} ${project.slug} ${project.gitRepository ?? \"\"}`}\n >\n {previews.length > 0 ? (\n <Previews project={project} latest={latest} previews={previews} />\n ) : null}\n <div class={cardBody}>\n <div class={cardTitleRow}>\n <h2 class={cardTitle}>\n <a href={urls.library}>{project.name}</a>\n </h2>\n {latest ? <PendingBadge pending={pending} href={urls.review} /> : null}\n </div>\n <Meta>\n {project.gitRepository ?? project.slug} · default{\" \"}\n <Badge tone=\"neutral\">{project.gitDefaultBranch}</Badge>\n </Meta>\n <LatestBuild latest={latest} />\n </div>\n <div class={cardFooter}>\n <Button variant=\"secondary\" size=\"sm\" icon=\"image\" href={urls.library}>\n Library\n </Button>\n <Button variant=\"ghost\" size=\"sm\" icon=\"external-link\" href={urls.storybook}>\n Storybook\n </Button>\n <Button variant=\"ghost\" size=\"sm\" icon=\"settings\" href={urls.settings}>\n Settings\n </Button>\n </div>\n </Card>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"projects-card.mjs","names":[],"sources":["../../src/pages/projects-card.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { HealthBadge } from \"../insights/health-panel.ts\";\nimport {\n Badge,\n Button,\n Card,\n Meta,\n RelativeTime,\n Thumbnail,\n statusTone,\n} from \"../ui/components.tsx\";\nimport { css } from \"../ui/css.ts\";\nimport { healthTone } from \"./health-panel.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\nconst thumbStrip = css`\n /* project-thumbs */\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: var(--space-2);\n padding: var(--space-3);\n background: var(--accent-wash);\n border-bottom: 1px solid var(--border-subtle);\n`;\n\nconst cardBody = css`\n /* project-body */\n flex: 1;\n display: grid;\n align-content: start;\n gap: var(--space-2);\n padding: var(--space-4);\n`;\n\nconst cardTitleRow = css`\n /* project-title-row */\n display: flex;\n gap: var(--space-2);\n align-items: flex-start;\n justify-content: space-between;\n`;\n\nconst cardTitle = css`\n /* project-title */\n margin: 0;\n font-size: var(--text-lg);\n font-weight: 650;\n letter-spacing: -0.01em;\n & a {\n color: var(--text-primary);\n }\n & a:hover {\n color: var(--accent-fg);\n }\n`;\n\nconst latestRow = css`\n /* project-latest */\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n align-items: center;\n color: var(--text-secondary);\n font-size: var(--text-sm);\n`;\n\nconst cardFooter = css`\n /* project-footer */\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n padding: var(--space-3) var(--space-4);\n border-top: 1px solid var(--border-subtle);\n`;\n\n/** What the overview shows about a project's most recent build. */\nexport interface ProjectSummary {\n project: Project;\n latest: Build | null;\n /** Up to three screenshots of the latest build. */\n previews: Snapshot[];\n /** Snapshots in the latest build still waiting for a decision. */\n pending: number;\n /** AI health digest (last 30 days) when one has finished. */\n health?: HealthBadge | null;\n}\n\nfunction PendingBadge({\n pending,\n href,\n}: {\n pending: number;\n href: string;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n if (pending === 0) {\n return (\n <Badge tone=\"success\" icon>\n All clear\n </Badge>\n );\n }\n return (\n <a href={href}>\n <Badge tone=\"warning\" icon>\n {pending} to review\n </Badge>\n </a>\n );\n}\n\nfunction LatestBuild({\n latest,\n}: {\n latest: Build | null;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n if (!latest) {\n return <Meta>No builds yet — upload one from CI.</Meta>;\n }\n return (\n <div class={latestRow}>\n <Meta as=\"span\" mono>\n {latest.gitBranch} · {latest.gitSha.slice(0, 7)}\n </Meta>\n <Badge tone={statusTone(latest.status)}>{latest.status}</Badge>\n <RelativeTime value={latest.createdAt} />\n </div>\n );\n}\n\nfunction Previews({\n project,\n latest,\n previews,\n}: Pick<ProjectSummary, \"project\" | \"latest\" | \"previews\">):\n | HtmlEscapedString\n | Promise<HtmlEscapedString> {\n return (\n <div class={thumbStrip}>\n {[0, 1, 2].map((slot): HtmlEscapedString | Promise<HtmlEscapedString> => {\n const snap = previews[slot];\n return (\n <Thumbnail\n key={String(slot)}\n src={\n snap && latest\n ? `/api/v1/projects/${project.slug}/builds/${latest.id}/snapshots/${snap.id}/image`\n : null\n }\n alt={snap ? `${snap.storyTitle} / ${snap.storyName}` : \"\"}\n placeholder=\"\"\n />\n );\n })}\n </div>\n );\n}\n\n/** One project in the overview grid: previews, latest build, review state, actions. */\nexport function ProjectCard(props: {\n summary: ProjectSummary;\n urls: { library: string; settings: string; storybook: string; review: string };\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { summary, urls } = props;\n const { project, latest, previews, pending } = summary;\n return (\n <Card\n fill\n data-filter-item\n data-filter-text={`${project.name} ${project.slug} ${project.gitRepository ?? \"\"}`}\n >\n {previews.length > 0 ? (\n <Previews project={project} latest={latest} previews={previews} />\n ) : null}\n <div class={cardBody}>\n <div class={cardTitleRow}>\n <h2 class={cardTitle}>\n <a href={urls.library}>{project.name}</a>\n </h2>\n {latest ? <PendingBadge pending={pending} href={urls.review} /> : null}\n </div>\n <Meta>\n {project.gitRepository ?? project.slug} · default{\" \"}\n <Badge tone=\"neutral\">{project.gitDefaultBranch}</Badge>\n </Meta>\n <LatestBuild latest={latest} />\n {summary.health ? (\n <a href={urls.library}>\n <Badge tone={healthTone(summary.health.verdict)} icon>\n health: {summary.health.verdict}\n {summary.health.score === null ? \"\" : ` · ${Math.round(summary.health.score)}`}\n </Badge>\n </a>\n ) : null}\n </div>\n <div class={cardFooter}>\n <Button variant=\"secondary\" size=\"sm\" icon=\"image\" href={urls.library}>\n Library\n </Button>\n <Button variant=\"ghost\" size=\"sm\" icon=\"external-link\" href={urls.storybook}>\n Storybook\n </Button>\n <Button variant=\"ghost\" size=\"sm\" icon=\"settings\" href={urls.settings}>\n Settings\n </Button>\n </div>\n </Card>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,MAAM,aAAa,GAAG;;;;;;;;;AAUtB,MAAM,WAAW,GAAG;;;;;;;;AASpB,MAAM,eAAe,GAAG;;;;;;;AAQxB,MAAM,YAAY,GAAG;;;;;;;;;;;;;AAcrB,MAAM,YAAY,GAAG;;;;;;;;;AAUrB,MAAM,aAAa,GAAG;;;;;;;;AAqBtB,SAAS,aAAa,EACpB,SACA,QAIiD;CACjD,IAAI,YAAY,GACd,OACE,oBAAC,OAAD;EAAO,MAAK;EAAU,MAAA;EAAK,UAAA;CAEpB,CAAA;CAGX,OACE,oBAAC,KAAD;EAAS;EACP,UAAA,qBAAC,OAAD;GAAO,MAAK;GAAU,MAAA;GAAtB,UAAA,CACG,SAAQ,YACJ;;CACN,CAAA;AAEP;AAEA,SAAS,YAAY,EACnB,UAGiD;CACjD,IAAI,CAAC,QACH,OAAO,oBAAC,MAAD,EAAA,UAAM,sCAAyC,CAAA;CAExD,OACE,qBAAC,OAAD;EAAK,OAAO;EAAZ,UAAA;GACE,qBAAC,MAAD;IAAM,IAAG;IAAO,MAAA;IAAhB,UAAA;KACG,OAAO;KAAU;KAAI,OAAO,OAAO,MAAM,GAAG,CAAC;IAC1C;;GACN,oBAAC,OAAD;IAAO,MAAM,WAAW,OAAO,MAAM;IAAI,UAAA,OAAO;GAAc,CAAA;GAC9D,oBAAC,cAAD,EAAc,OAAO,OAAO,UAAY,CAAA;EACrC;;AAET;AAEA,SAAS,SAAS,EAChB,SACA,QACA,YAG6B;CAC7B,OACE,oBAAC,OAAD;EAAK,OAAO;EACT,UAAA;GAAC;GAAG;GAAG;EAAC,CAAC,CAAC,KAAK,SAAyD;GACvE,MAAM,OAAO,SAAS;GACtB,OACE,oBAAC,WAAD;IAEE,KACE,QAAQ,SACJ,oBAAoB,QAAQ,KAAK,UAAU,OAAO,GAAG,aAAa,KAAK,GAAG,UAC1E;IAEN,KAAK,OAAO,GAAG,KAAK,WAAW,KAAK,KAAK,cAAc;IACvD,aAAY;GACb,GARM,OAAO,IAAI,CAQjB;EAEL,CAAC;CACE,CAAA;AAET;;AAGA,SAAgB,YAAY,OAGuB;CACjD,MAAM,EAAE,SAAS,SAAS;CAC1B,MAAM,EAAE,SAAS,QAAQ,UAAU,YAAY;CAC/C,OACE,qBAAC,MAAD;EACE,MAAA;EACA,oBAAA;EACA,oBAAkB,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,GAAG,QAAQ,iBAAiB;EAHhF,UAAA;GAKG,SAAS,SAAS,IACjB,oBAAC,UAAD;IAAmB;IAAiB;IAAkB;GAAW,CAAA,IAC/D;GACJ,qBAAC,OAAD;IAAK,OAAO;IAAZ,UAAA;KACE,qBAAC,OAAD;MAAK,OAAO;MAAZ,UAAA,CACE,oBAAC,MAAD;OAAI,OAAO;OACT,UAAA,oBAAC,KAAD;QAAG,MAAM,KAAK;QAAU,UAAA,QAAQ;OAAQ,CAAA;MACtC,CAAA,GACH,SAAS,oBAAC,cAAD;OAAuB;OAAS,MAAM,KAAK;MAAS,CAAA,IAAI,IAC/D;;KACL,qBAAC,MAAD,EAAA,UAAA;MACG,QAAQ,iBAAiB,QAAQ;MAAK;MAAW;MAClD,oBAAC,OAAD;OAAO,MAAK;OAAW,UAAA,QAAQ;MAAwB,CAAA;KACnD,EAAA,CAAA;KACN,oBAAC,aAAD,EAAqB,OAAS,CAAA;KAC7B,QAAQ,SACP,oBAAC,KAAD;MAAG,MAAM,KAAK;MACZ,UAAA,qBAAC,OAAD;OAAO,MAAM,WAAW,QAAQ,OAAO,OAAO;OAAG,MAAA;OAAjD,UAAA;QAAsD;QAC3C,QAAQ,OAAO;QACvB,QAAQ,OAAO,UAAU,OAAO,KAAK,MAAM,KAAK,MAAM,QAAQ,OAAO,KAAK;OACtE;;KACN,CAAA,IACD;IACD;;GACL,qBAAC,OAAD;IAAK,OAAO;IAAZ,UAAA;KACE,oBAAC,QAAD;MAAQ,SAAQ;MAAY,MAAK;MAAK,MAAK;MAAQ,MAAM,KAAK;MAAS,UAAA;KAE/D,CAAA;KACR,oBAAC,QAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,MAAK;MAAgB,MAAM,KAAK;MAAW,UAAA;KAErE,CAAA;KACR,oBAAC,QAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,MAAK;MAAW,MAAM,KAAK;MAAU,UAAA;KAE/D,CAAA;IACL;;EACD;;AAEV"}
|
package/dist/pages/projects.mjs
CHANGED
|
@@ -10,11 +10,12 @@ import { HStack, VStack } from "../ui/stacks.mjs";
|
|
|
10
10
|
import { CodeBlock } from "../ui/code-block.mjs";
|
|
11
11
|
import { FilterInput } from "../ui/filter-input.mjs";
|
|
12
12
|
import { DocumentLayout } from "../ui/document.mjs";
|
|
13
|
+
import { loadHealthBadge } from "../insights/health-panel.mjs";
|
|
13
14
|
import { ProjectCard } from "./projects-card.mjs";
|
|
14
15
|
import { BuildModel, ProjectModel, SnapshotModel } from "@storyshelf/core/models";
|
|
15
16
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
16
17
|
import { createUrlBuilder } from "@storyshelf/core/urls";
|
|
17
|
-
|
|
18
|
+
// src/pages/projects.tsx
|
|
18
19
|
const projectGrid = css`
|
|
19
20
|
/* project-grid */
|
|
20
21
|
display: grid;
|
|
@@ -32,11 +33,13 @@ const setupSummary = css`
|
|
|
32
33
|
async function summarize(project) {
|
|
33
34
|
const { db } = getStore();
|
|
34
35
|
const [latest] = await new BuildModel(db).list(project.id);
|
|
36
|
+
const health = await loadHealthBadge(project);
|
|
35
37
|
if (!latest) return {
|
|
36
38
|
project,
|
|
37
39
|
latest: null,
|
|
38
40
|
previews: [],
|
|
39
|
-
pending: 0
|
|
41
|
+
pending: 0,
|
|
42
|
+
health
|
|
40
43
|
};
|
|
41
44
|
const snapshots = await new SnapshotModel(db).listByBuild(latest.id);
|
|
42
45
|
const pending = snapshots.filter((snap) => snap.status === "new" || snap.status === "changed");
|
|
@@ -44,7 +47,8 @@ async function summarize(project) {
|
|
|
44
47
|
project,
|
|
45
48
|
latest,
|
|
46
49
|
previews: (pending.length > 0 ? pending : snapshots).slice(0, 3),
|
|
47
|
-
pending: pending.length
|
|
50
|
+
pending: pending.length,
|
|
51
|
+
health
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
54
|
/** Copy-ready steps to get a first build uploaded. */
|
|
@@ -123,7 +127,6 @@ async function renderProjectsPage() {
|
|
|
123
127
|
})]
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
|
-
//#endregion
|
|
127
130
|
export { renderProjectsPage };
|
|
128
131
|
|
|
129
132
|
//# sourceMappingURL=projects.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects.mjs","names":[],"sources":["../../src/pages/projects.tsx"],"sourcesContent":["import { BuildModel, ProjectModel, SnapshotModel } from \"@storyshelf/core/models\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport { getStore } from \"../store.ts\";\nimport {\n Button,\n Card,\n CodeBlock,\n EmptyState,\n FilterInput,\n HStack,\n Meta,\n PageHeader,\n VStack,\n} from \"../ui/components.tsx\";\nimport { css } from \"../ui/css.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { ProjectCard, type ProjectSummary } from \"./projects-card.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\nconst projectGrid = css`\n /* project-grid */\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));\n gap: var(--space-4);\n`;\n\nconst setupSummary = css`\n /* setup-summary */\n cursor: pointer;\n font-weight: 650;\n font-size: var(--text-lg);\n letter-spacing: -0.01em;\n`;\n\n/** Latest build, a few previews, and the review backlog for one project. */\nasync function summarize(project: Project): Promise<ProjectSummary> {\n const { db } = getStore();\n const [latest] = await new BuildModel(db).list(project.id);\n if (!latest) {\n return { project, latest: null, previews: [], pending: 0 };\n }\n const snapshots = await new SnapshotModel(db).listByBuild(latest.id);\n const pending = snapshots.filter((snap) => snap.status === \"new\" || snap.status === \"changed\");\n const preferred = pending.length > 0 ? pending : snapshots;\n return { project, latest, previews: preferred.slice(0, 3), pending: pending.length };\n}\n\n/** Copy-ready steps to get a first build uploaded. */\nfunction SetupGuide({ open }: { open: boolean }): ReturnType<typeof Card> {\n return (\n <Card>\n <details open={open}>\n <summary class={setupSummary}>Set up your first upload</summary>\n <VStack>\n <Meta>1. Create a project (writes .storybook/storyshelf.json for later runs).</Meta>\n <CodeBlock code='npx storyshelf create --url http://localhost:3000 --name \"My Storybook\" --token $STORYSHELF_ADMIN_TOKEN' />\n <Meta>\n 2. Generate a token in <strong>Settings → Tokens</strong> and set it in CI.\n </Meta>\n <CodeBlock code=\"export STORYSHELF_TOKEN=<your token>\" />\n <Meta>3. Upload your built Storybook.</Meta>\n <CodeBlock code=\"npx storyshelf upload\" />\n </VStack>\n </details>\n </Card>\n );\n}\n\n/** Projects overview: searchable project cards with review state, plus setup help. */\nexport async function renderProjectsPage(): Promise<RenderedContent> {\n const { db, user, config } = getStore();\n const urls = createUrlBuilder(\"/\", config.publishedBaseDomain);\n const projects = await new ProjectModel(db).list();\n const summaries = await Promise.all(projects.map((project) => summarize(project)));\n const canCreate = !user || user.role === \"admin\" || user.role === \"member\";\n const newProject = canCreate ? (\n <Button variant=\"primary\" icon=\"plus\" href={urls.projectsNew()}>\n New project\n </Button>\n ) : null;\n\n return (\n <DocumentLayout title=\"Projects\" nav={{ active: \"projects\" }}>\n <PageHeader\n title=\"Projects\"\n description=\"Each project is one Storybook. Create a project, then upload builds from CI.\"\n actions={newProject}\n />\n <VStack gap=\"lg\">\n {projects.length > 1 ? (\n <HStack>\n <FilterInput label=\"Filter projects\" placeholder=\"Filter projects…\" />\n </HStack>\n ) : null}\n {projects.length === 0 ? (\n <EmptyState\n title=\"No projects yet\"\n description=\"Create your first project to start visual testing. Projects are free and unlimited.\"\n action={newProject}\n />\n ) : (\n <div class={projectGrid} data-filter-scope>\n {summaries.map((summary) => (\n <ProjectCard\n key={summary.project.id}\n summary={summary}\n urls={{\n library: urls.library(summary.project.slug),\n settings: urls.settings(summary.project.slug),\n storybook: urls.short(summary.project.slug),\n review: summary.latest\n ? urls.buildDiff(summary.project.slug, summary.latest.id)\n : urls.library(summary.project.slug),\n }}\n />\n ))}\n <div data-filter-empty hidden>\n <Meta center>No projects match your filter.</Meta>\n </div>\n </div>\n )}\n <SetupGuide open={projects.length === 0} />\n </VStack>\n </DocumentLayout>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"projects.mjs","names":[],"sources":["../../src/pages/projects.tsx"],"sourcesContent":["import { BuildModel, ProjectModel, SnapshotModel } from \"@storyshelf/core/models\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport { loadHealthBadge } from \"../insights/health-panel.ts\";\nimport { getStore } from \"../store.ts\";\nimport {\n Button,\n Card,\n CodeBlock,\n EmptyState,\n FilterInput,\n HStack,\n Meta,\n PageHeader,\n VStack,\n} from \"../ui/components.tsx\";\nimport { css } from \"../ui/css.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { ProjectCard, type ProjectSummary } from \"./projects-card.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\nconst projectGrid = css`\n /* project-grid */\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));\n gap: var(--space-4);\n`;\n\nconst setupSummary = css`\n /* setup-summary */\n cursor: pointer;\n font-weight: 650;\n font-size: var(--text-lg);\n letter-spacing: -0.01em;\n`;\n\n/** Latest build, a few previews, and the review backlog for one project. */\nasync function summarize(project: Project): Promise<ProjectSummary> {\n const { db } = getStore();\n const [latest] = await new BuildModel(db).list(project.id);\n const health = await loadHealthBadge(project);\n if (!latest) {\n return { project, latest: null, previews: [], pending: 0, health };\n }\n const snapshots = await new SnapshotModel(db).listByBuild(latest.id);\n const pending = snapshots.filter((snap) => snap.status === \"new\" || snap.status === \"changed\");\n const preferred = pending.length > 0 ? pending : snapshots;\n return { project, latest, previews: preferred.slice(0, 3), pending: pending.length, health };\n}\n\n/** Copy-ready steps to get a first build uploaded. */\nfunction SetupGuide({ open }: { open: boolean }): ReturnType<typeof Card> {\n return (\n <Card>\n <details open={open}>\n <summary class={setupSummary}>Set up your first upload</summary>\n <VStack>\n <Meta>1. Create a project (writes .storybook/storyshelf.json for later runs).</Meta>\n <CodeBlock code='npx storyshelf create --url http://localhost:3000 --name \"My Storybook\" --token $STORYSHELF_ADMIN_TOKEN' />\n <Meta>\n 2. Generate a token in <strong>Settings → Tokens</strong> and set it in CI.\n </Meta>\n <CodeBlock code=\"export STORYSHELF_TOKEN=<your token>\" />\n <Meta>3. Upload your built Storybook.</Meta>\n <CodeBlock code=\"npx storyshelf upload\" />\n </VStack>\n </details>\n </Card>\n );\n}\n\n/** Projects overview: searchable project cards with review state, plus setup help. */\nexport async function renderProjectsPage(): Promise<RenderedContent> {\n const { db, user, config } = getStore();\n const urls = createUrlBuilder(\"/\", config.publishedBaseDomain);\n const projects = await new ProjectModel(db).list();\n const summaries = await Promise.all(projects.map((project) => summarize(project)));\n const canCreate = !user || user.role === \"admin\" || user.role === \"member\";\n const newProject = canCreate ? (\n <Button variant=\"primary\" icon=\"plus\" href={urls.projectsNew()}>\n New project\n </Button>\n ) : null;\n\n return (\n <DocumentLayout title=\"Projects\" nav={{ active: \"projects\" }}>\n <PageHeader\n title=\"Projects\"\n description=\"Each project is one Storybook. Create a project, then upload builds from CI.\"\n actions={newProject}\n />\n <VStack gap=\"lg\">\n {projects.length > 1 ? (\n <HStack>\n <FilterInput label=\"Filter projects\" placeholder=\"Filter projects…\" />\n </HStack>\n ) : null}\n {projects.length === 0 ? (\n <EmptyState\n title=\"No projects yet\"\n description=\"Create your first project to start visual testing. Projects are free and unlimited.\"\n action={newProject}\n />\n ) : (\n <div class={projectGrid} data-filter-scope>\n {summaries.map((summary) => (\n <ProjectCard\n key={summary.project.id}\n summary={summary}\n urls={{\n library: urls.library(summary.project.slug),\n settings: urls.settings(summary.project.slug),\n storybook: urls.short(summary.project.slug),\n review: summary.latest\n ? urls.buildDiff(summary.project.slug, summary.latest.id)\n : urls.library(summary.project.slug),\n }}\n />\n ))}\n <div data-filter-empty hidden>\n <Meta center>No projects match your filter.</Meta>\n </div>\n </div>\n )}\n <SetupGuide open={projects.length === 0} />\n </VStack>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { getStore } from "../store.mjs";
|
|
5
|
+
import { Button } from "../ui/buttons.mjs";
|
|
6
|
+
import { Badge, EmptyState, Meta } from "../ui/feedback.mjs";
|
|
7
|
+
import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
8
|
+
import { HStack, VStack } from "../ui/stacks.mjs";
|
|
9
|
+
import { Table } from "../ui/table.mjs";
|
|
10
|
+
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
+
import { createUrlBuilder } from "@storyshelf/core/urls";
|
|
12
|
+
// src/pages/review-changeset.tsx
|
|
13
|
+
/** What the build changed in the Storybook catalog, against its baseline build. */
|
|
14
|
+
function ChangesetCard(props) {
|
|
15
|
+
const { data } = props;
|
|
16
|
+
const { baseline, changeset } = data.changeset;
|
|
17
|
+
const total = changeset.added.length + changeset.changed.length + changeset.removed.length;
|
|
18
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
19
|
+
/* @__PURE__ */ jsx(SectionTitle, { children: "Changes in this build" }),
|
|
20
|
+
/* @__PURE__ */ jsxs(Meta, { children: [
|
|
21
|
+
baseline ? `Compared with the accepted build ${baseline.gitSha.slice(0, 7)} on ${baseline.gitBranch}.` : "No accepted build to compare with: every component counts as new.",
|
|
22
|
+
" ",
|
|
23
|
+
"A changed component has different stories, not necessarily a different look."
|
|
24
|
+
] }),
|
|
25
|
+
total === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
26
|
+
title: "No component changes",
|
|
27
|
+
description: `${changeset.unchanged} components are the same as in the baseline.`
|
|
28
|
+
}) : /* @__PURE__ */ jsx(ChangesetTable, { data })
|
|
29
|
+
] });
|
|
30
|
+
}
|
|
31
|
+
function ChangesetTable(props) {
|
|
32
|
+
const { data } = props;
|
|
33
|
+
const { changeset } = data.changeset;
|
|
34
|
+
return /* @__PURE__ */ jsxs(VStack, { children: [/* @__PURE__ */ jsxs(HStack, { children: [
|
|
35
|
+
/* @__PURE__ */ jsxs(Badge, {
|
|
36
|
+
tone: "success",
|
|
37
|
+
children: [changeset.added.length, " added"]
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ jsxs(Badge, {
|
|
40
|
+
tone: "warning",
|
|
41
|
+
children: [changeset.changed.length, " changed"]
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ jsxs(Badge, {
|
|
44
|
+
tone: "danger",
|
|
45
|
+
children: [changeset.removed.length, " removed"]
|
|
46
|
+
}),
|
|
47
|
+
/* @__PURE__ */ jsxs(Meta, {
|
|
48
|
+
as: "span",
|
|
49
|
+
children: [changeset.unchanged, " unchanged"]
|
|
50
|
+
})
|
|
51
|
+
] }), /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs("table", { children: [
|
|
52
|
+
/* @__PURE__ */ jsx("caption", {
|
|
53
|
+
class: "visually-hidden",
|
|
54
|
+
children: "Components that changed in this build"
|
|
55
|
+
}),
|
|
56
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
57
|
+
/* @__PURE__ */ jsx("th", { children: "Component" }),
|
|
58
|
+
/* @__PURE__ */ jsx("th", { children: "Change" }),
|
|
59
|
+
/* @__PURE__ */ jsx("th", { children: "Stories" }),
|
|
60
|
+
/* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx("span", {
|
|
61
|
+
class: "visually-hidden",
|
|
62
|
+
children: "Links"
|
|
63
|
+
}) })
|
|
64
|
+
] }) }),
|
|
65
|
+
/* @__PURE__ */ jsxs("tbody", { children: [
|
|
66
|
+
rows(data, "added", changeset.added),
|
|
67
|
+
rows(data, "changed", changeset.changed),
|
|
68
|
+
rows(data, "removed", changeset.removed)
|
|
69
|
+
] })
|
|
70
|
+
] }) })] });
|
|
71
|
+
}
|
|
72
|
+
function rows(data, kind, items) {
|
|
73
|
+
return items.map((item) => /* @__PURE__ */ jsx(ChangeRow, {
|
|
74
|
+
data,
|
|
75
|
+
kind,
|
|
76
|
+
item
|
|
77
|
+
}, `${kind}:${item.importPath}:${item.title}`));
|
|
78
|
+
}
|
|
79
|
+
const KIND_TONE = {
|
|
80
|
+
added: "success",
|
|
81
|
+
changed: "warning",
|
|
82
|
+
removed: "danger"
|
|
83
|
+
};
|
|
84
|
+
function ChangeRow(props) {
|
|
85
|
+
const { data, kind, item } = props;
|
|
86
|
+
const { project, build } = data;
|
|
87
|
+
const urls = createUrlBuilder("/", getStore().config.publishedBaseDomain);
|
|
88
|
+
const story = data.firstStory.get(item.importPath);
|
|
89
|
+
return /* @__PURE__ */ jsxs("tr", { children: [
|
|
90
|
+
/* @__PURE__ */ jsxs("td", { children: [/* @__PURE__ */ jsx("strong", { children: item.title }), /* @__PURE__ */ jsx(Meta, {
|
|
91
|
+
as: "div",
|
|
92
|
+
mono: true,
|
|
93
|
+
children: item.importPath
|
|
94
|
+
})] }),
|
|
95
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Badge, {
|
|
96
|
+
tone: KIND_TONE[kind],
|
|
97
|
+
children: kind
|
|
98
|
+
}) }),
|
|
99
|
+
/* @__PURE__ */ jsx("td", { children: item.previousStoryCount !== void 0 && item.previousStoryCount !== item.storyCount ? `${item.previousStoryCount} → ${item.storyCount}` : item.storyCount }),
|
|
100
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsxs(HStack, { children: [kind !== "removed" && story ? /* @__PURE__ */ jsx(Button, {
|
|
101
|
+
variant: "ghost",
|
|
102
|
+
size: "sm",
|
|
103
|
+
href: `${urls.storybookBuild(project.slug, build.id)}/?path=/story/${encodeURIComponent(story)}`,
|
|
104
|
+
children: "Story"
|
|
105
|
+
}) : null, item.importPath ? /* @__PURE__ */ jsx(Button, {
|
|
106
|
+
variant: "ghost",
|
|
107
|
+
size: "sm",
|
|
108
|
+
href: urls.component(project.slug, item.importPath),
|
|
109
|
+
children: "Component"
|
|
110
|
+
}) : null] }) })
|
|
111
|
+
] });
|
|
112
|
+
}
|
|
113
|
+
export { ChangesetCard };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=review-changeset.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-changeset.mjs","names":[],"sources":["../../src/pages/review-changeset.tsx"],"sourcesContent":["/** The changeset card (ADR 0013, decision 8): components the build added, removed or changed. */\nimport type { ChangesetComponent } from \"@storyshelf/core/models\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { ReviewSectionData } from \"../routers/review-section.handlers.ts\";\nimport { getStore } from \"../store.ts\";\nimport {\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n SectionTitle,\n Table,\n VStack,\n} from \"../ui/components.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype Rendered = HtmlEscapedString | Promise<HtmlEscapedString>;\n\ntype Kind = \"added\" | \"changed\" | \"removed\";\n\n/** What the build changed in the Storybook catalog, against its baseline build. */\nexport function ChangesetCard(props: { data: ReviewSectionData }): Rendered {\n const { data } = props;\n const { baseline, changeset } = data.changeset;\n const total = changeset.added.length + changeset.changed.length + changeset.removed.length;\n return (\n <Card>\n <SectionTitle>Changes in this build</SectionTitle>\n <Meta>\n {baseline\n ? `Compared with the accepted build ${baseline.gitSha.slice(0, 7)} on ${baseline.gitBranch}.`\n : \"No accepted build to compare with: every component counts as new.\"}{\" \"}\n A changed component has different stories, not necessarily a different look.\n </Meta>\n {total === 0 ? (\n <EmptyState\n title=\"No component changes\"\n description={`${changeset.unchanged} components are the same as in the baseline.`}\n />\n ) : (\n <ChangesetTable data={data} />\n )}\n </Card>\n );\n}\n\nfunction ChangesetTable(props: { data: ReviewSectionData }): Rendered {\n const { data } = props;\n const { changeset } = data.changeset;\n return (\n <VStack>\n <HStack>\n <Badge tone=\"success\">{changeset.added.length} added</Badge>\n <Badge tone=\"warning\">{changeset.changed.length} changed</Badge>\n <Badge tone=\"danger\">{changeset.removed.length} removed</Badge>\n <Meta as=\"span\">{changeset.unchanged} unchanged</Meta>\n </HStack>\n <Table>\n <table>\n <caption class=\"visually-hidden\">Components that changed in this build</caption>\n <thead>\n <tr>\n <th>Component</th>\n <th>Change</th>\n <th>Stories</th>\n <th>\n <span class=\"visually-hidden\">Links</span>\n </th>\n </tr>\n </thead>\n <tbody>\n {rows(data, \"added\", changeset.added)}\n {rows(data, \"changed\", changeset.changed)}\n {rows(data, \"removed\", changeset.removed)}\n </tbody>\n </table>\n </Table>\n </VStack>\n );\n}\n\nfunction rows(\n data: ReviewSectionData,\n kind: Kind,\n items: readonly (ChangesetComponent & { previousStoryCount?: number })[],\n): Rendered[] {\n return items.map((item): Rendered => (\n <ChangeRow\n key={`${kind}:${item.importPath}:${item.title}`}\n data={data}\n kind={kind}\n item={item}\n />\n ));\n}\n\nconst KIND_TONE = { added: \"success\", changed: \"warning\", removed: \"danger\" } as const;\n\nfunction ChangeRow(props: {\n data: ReviewSectionData;\n kind: Kind;\n item: ChangesetComponent & { previousStoryCount?: number };\n}): Rendered {\n const { data, kind, item } = props;\n const { project, build } = data;\n const urls = createUrlBuilder(\"/\", getStore().config.publishedBaseDomain);\n const story = data.firstStory.get(item.importPath);\n return (\n <tr>\n <td>\n <strong>{item.title}</strong>\n <Meta as=\"div\" mono>\n {item.importPath}\n </Meta>\n </td>\n <td>\n <Badge tone={KIND_TONE[kind]}>{kind}</Badge>\n </td>\n <td>\n {item.previousStoryCount !== undefined && item.previousStoryCount !== item.storyCount\n ? `${item.previousStoryCount} → ${item.storyCount}`\n : item.storyCount}\n </td>\n <td>\n <HStack>\n {kind !== \"removed\" && story ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n href={`${urls.storybookBuild(project.slug, build.id)}/?path=/story/${encodeURIComponent(story)}`}\n >\n Story\n </Button>\n ) : null}\n {item.importPath ? (\n <Button variant=\"ghost\" size=\"sm\" href={urls.component(project.slug, item.importPath)}>\n Component\n </Button>\n ) : null}\n </HStack>\n </td>\n </tr>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { Button } from "../ui/buttons.mjs";
|
|
5
|
+
import { Badge, Meta } from "../ui/feedback.mjs";
|
|
6
|
+
import { TextareaField } from "../ui/forms.mjs";
|
|
7
|
+
import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
8
|
+
import { VStack } from "../ui/stacks.mjs";
|
|
9
|
+
import { csrfField } from "../ui/csrf-field.mjs";
|
|
10
|
+
import { CommentCard } from "./build-diff-comments.mjs";
|
|
11
|
+
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
12
|
+
// src/pages/review-comments.tsx
|
|
13
|
+
/** Comments on the build as a whole and on its stories, with a form for a build comment. */
|
|
14
|
+
function CommentsCard(props) {
|
|
15
|
+
const { data } = props;
|
|
16
|
+
const { project, build, comments, canComment } = data;
|
|
17
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
18
|
+
/* @__PURE__ */ jsx(SectionTitle, { children: "Comments" }),
|
|
19
|
+
comments.length === 0 ? /* @__PURE__ */ jsxs(Meta, { children: [
|
|
20
|
+
"No comments yet.",
|
|
21
|
+
" ",
|
|
22
|
+
canComment ? "Comment on the build here, or on a story from its component page." : "Developers, approvers and admins can comment."
|
|
23
|
+
] }) : null,
|
|
24
|
+
/* @__PURE__ */ jsx(VStack, { children: comments.map((comment) => /* @__PURE__ */ jsx(CommentCard, {
|
|
25
|
+
project,
|
|
26
|
+
build,
|
|
27
|
+
comment,
|
|
28
|
+
canReview: canComment,
|
|
29
|
+
anchor: /* @__PURE__ */ jsx(Badge, {
|
|
30
|
+
tone: "neutral",
|
|
31
|
+
children: comment.storyId ? `story ${comment.storyId}` : "build"
|
|
32
|
+
})
|
|
33
|
+
}, comment.id)) }),
|
|
34
|
+
canComment ? /* @__PURE__ */ jsx(CommentForm, {
|
|
35
|
+
project,
|
|
36
|
+
build,
|
|
37
|
+
storyId: void 0
|
|
38
|
+
}) : null
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
/** The comments of one story in the build a component page shows, with a form to add one. */
|
|
42
|
+
function StoryComments(props) {
|
|
43
|
+
const { project, build, storyId, canComment } = props;
|
|
44
|
+
const comments = props.comments.filter((comment) => comment.storyId === storyId);
|
|
45
|
+
return /* @__PURE__ */ jsxs(Card, {
|
|
46
|
+
id: "story-comments",
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsx(SectionTitle, { children: "Comments on this story" }),
|
|
49
|
+
/* @__PURE__ */ jsxs(Meta, { children: [
|
|
50
|
+
"On build ",
|
|
51
|
+
build.gitSha.slice(0, 7),
|
|
52
|
+
"."
|
|
53
|
+
] }),
|
|
54
|
+
comments.length === 0 ? /* @__PURE__ */ jsx(Meta, { children: "No comments on this story yet." }) : null,
|
|
55
|
+
/* @__PURE__ */ jsx(VStack, { children: comments.map((comment) => /* @__PURE__ */ jsx(CommentCard, {
|
|
56
|
+
project,
|
|
57
|
+
build,
|
|
58
|
+
comment,
|
|
59
|
+
canReview: canComment
|
|
60
|
+
}, comment.id)) }),
|
|
61
|
+
canComment ? /* @__PURE__ */ jsx(CommentForm, {
|
|
62
|
+
project,
|
|
63
|
+
build,
|
|
64
|
+
storyId
|
|
65
|
+
}) : null
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function CommentForm(props) {
|
|
70
|
+
const { project, build, storyId } = props;
|
|
71
|
+
const url = `/projects/${project.slug}/builds/${build.id}/comments`;
|
|
72
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
73
|
+
method: "post",
|
|
74
|
+
action: url,
|
|
75
|
+
"hx-post": url,
|
|
76
|
+
"hx-target": "body",
|
|
77
|
+
class: "mt-1 max-w-prose",
|
|
78
|
+
children: [csrfField(), /* @__PURE__ */ jsxs(VStack, { children: [
|
|
79
|
+
storyId ? /* @__PURE__ */ jsx("input", {
|
|
80
|
+
type: "hidden",
|
|
81
|
+
name: "storyId",
|
|
82
|
+
value: storyId
|
|
83
|
+
}) : null,
|
|
84
|
+
/* @__PURE__ */ jsx(TextareaField, {
|
|
85
|
+
label: storyId ? "Add story comment" : "Add build comment",
|
|
86
|
+
name: "body",
|
|
87
|
+
id: storyId ? "story-comment-body" : "build-comment-body",
|
|
88
|
+
rows: 3,
|
|
89
|
+
required: true,
|
|
90
|
+
placeholder: storyId ? "Leave a comment on this story…" : "Leave a comment on this build…"
|
|
91
|
+
}),
|
|
92
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
|
|
93
|
+
variant: "primary",
|
|
94
|
+
type: "submit",
|
|
95
|
+
children: "Comment"
|
|
96
|
+
}) })
|
|
97
|
+
] })]
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export { CommentsCard, StoryComments };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=review-comments.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-comments.mjs","names":[],"sources":["../../src/pages/review-comments.tsx"],"sourcesContent":["/** Build and story comments (ADR 0013, decision 2): the build page card and the story thread. */\nimport type { Build, Comment, Project } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { ReviewSectionData } from \"../routers/review-section.handlers.ts\";\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 { CommentCard } from \"./build-diff-comments.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype Rendered = HtmlEscapedString | Promise<HtmlEscapedString>;\n\n/** Comments on the build as a whole and on its stories, with a form for a build comment. */\nexport function CommentsCard(props: { data: ReviewSectionData }): Rendered {\n const { data } = props;\n const { project, build, comments, canComment } = data;\n return (\n <Card>\n <SectionTitle>Comments</SectionTitle>\n {comments.length === 0 ? (\n <Meta>\n No comments yet.{\" \"}\n {canComment\n ? \"Comment on the build here, or on a story from its component page.\"\n : \"Developers, approvers and admins can comment.\"}\n </Meta>\n ) : null}\n <VStack>\n {comments.map((comment): Rendered => (\n <CommentCard\n key={comment.id}\n project={project}\n build={build}\n comment={comment}\n canReview={canComment}\n anchor={\n <Badge tone=\"neutral\">{comment.storyId ? `story ${comment.storyId}` : \"build\"}</Badge>\n }\n />\n ))}\n </VStack>\n {canComment ? <CommentForm project={project} build={build} storyId={undefined} /> : null}\n </Card>\n );\n}\n\n/** The comments of one story in the build a component page shows, with a form to add one. */\nexport function StoryComments(props: {\n project: Project;\n build: Build;\n storyId: string;\n comments: readonly Comment[];\n canComment: boolean;\n}): Rendered {\n const { project, build, storyId, canComment } = props;\n const comments = props.comments.filter((comment) => comment.storyId === storyId);\n return (\n <Card id=\"story-comments\">\n <SectionTitle>Comments on this story</SectionTitle>\n <Meta>On build {build.gitSha.slice(0, 7)}.</Meta>\n {comments.length === 0 ? <Meta>No comments on this story yet.</Meta> : null}\n <VStack>\n {comments.map((comment): Rendered => (\n <CommentCard\n key={comment.id}\n project={project}\n build={build}\n comment={comment}\n canReview={canComment}\n />\n ))}\n </VStack>\n {canComment ? <CommentForm project={project} build={build} storyId={storyId} /> : null}\n </Card>\n );\n}\n\nfunction CommentForm(props: {\n project: Project;\n build: Build;\n storyId: string | undefined;\n}): Rendered {\n const { project, build, storyId } = props;\n const url = `/projects/${project.slug}/builds/${build.id}/comments`;\n return (\n <form method=\"post\" action={url} hx-post={url} hx-target=\"body\" class=\"mt-1 max-w-prose\">\n {csrfField()}\n <VStack>\n {storyId ? <input type=\"hidden\" name=\"storyId\" value={storyId} /> : null}\n <TextareaField\n label={storyId ? \"Add story comment\" : \"Add build comment\"}\n name=\"body\"\n id={storyId ? \"story-comment-body\" : \"build-comment-body\"}\n rows={3}\n required\n placeholder={\n storyId ? \"Leave a comment on this story…\" : \"Leave a comment on this build…\"\n }\n />\n <div>\n <Button variant=\"primary\" type=\"submit\">\n Comment\n </Button>\n </div>\n </VStack>\n </form>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAqBA,SAAgB,aAAa,OAA8C;CACzE,MAAM,EAAE,SAAS;CACjB,MAAM,EAAE,SAAS,OAAO,UAAU,eAAe;CACjD,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,oBAAC,cAAD,EAAA,UAAc,WAAsB,CAAA;EACnC,SAAS,WAAW,IACnB,qBAAC,MAAD,EAAA,UAAA;GAAM;GACa;GAChB,aACG,sEACA;EACA,EAAA,CAAA,IACJ;EACJ,oBAAC,QAAD,EAAA,UACG,SAAS,KAAK,YACb,oBAAC,aAAD;GAEW;GACF;GACE;GACT,WAAW;GACX,QACE,oBAAC,OAAD;IAAO,MAAK;IAAW,UAAA,QAAQ,UAAU,SAAS,QAAQ,YAAY;GAAe,CAAA;EAExF,GARM,QAAQ,EAQd,CACF,EACK,CAAA;EACP,aAAa,oBAAC,aAAD;GAAsB;GAAgB;GAAO,SAAS,KAAA;EAAY,CAAA,IAAI;CAChF,EAAA,CAAA;AAEV;;AAGA,SAAgB,cAAc,OAMjB;CACX,MAAM,EAAE,SAAS,OAAO,SAAS,eAAe;CAChD,MAAM,WAAW,MAAM,SAAS,QAAQ,YAAY,QAAQ,YAAY,OAAO;CAC/E,OACE,qBAAC,MAAD;EAAM,IAAG;EAAT,UAAA;GACE,oBAAC,cAAD,EAAA,UAAc,yBAAoC,CAAA;GAClD,qBAAC,MAAD,EAAA,UAAA;IAAM;IAAU,MAAM,OAAO,MAAM,GAAG,CAAC;IAAE;GAAO,EAAA,CAAA;GAC/C,SAAS,WAAW,IAAI,oBAAC,MAAD,EAAA,UAAM,iCAAoC,CAAA,IAAI;GACvE,oBAAC,QAAD,EAAA,UACG,SAAS,KAAK,YACb,oBAAC,aAAD;IAEW;IACF;IACE;IACT,WAAW;GACZ,GALM,QAAQ,EAKd,CACF,EACK,CAAA;GACP,aAAa,oBAAC,aAAD;IAAsB;IAAgB;IAAgB;GAAU,CAAA,IAAI;EAC9E;;AAEV;AAEA,SAAS,YAAY,OAIR;CACX,MAAM,EAAE,SAAS,OAAO,YAAY;CACpC,MAAM,MAAM,aAAa,QAAQ,KAAK,UAAU,MAAM,GAAG;CACzD,OACE,qBAAC,QAAD;EAAM,QAAO;EAAO,QAAQ;EAAK,WAAS;EAAK,aAAU;EAAO,OAAM;EAAtE,UAAA,CACG,UAAU,GACX,qBAAC,QAAD,EAAA,UAAA;GACG,UAAU,oBAAC,SAAD;IAAO,MAAK;IAAS,MAAK;IAAU,OAAO;GAAU,CAAA,IAAI;GACpE,oBAAC,eAAD;IACE,OAAO,UAAU,sBAAsB;IACvC,MAAK;IACL,IAAI,UAAU,uBAAuB;IACrC,MAAM;IACN,UAAA;IACA,aACE,UAAU,mCAAmC;GAEhD,CAAA;GACD,oBAAC,OAAD,EAAA,UACE,oBAAC,QAAD;IAAQ,SAAQ;IAAU,MAAK;IAAS,UAAA;GAEhC,CAAA,EACL,CAAA;EACC,EAAA,CAAA,CACJ;;AAEV"}
|