Skip to content

feat: migrate health score displays to v2 and implement Health Score v2 content spec IN-1212 - #2054

Open
gaspergrom wants to merge 12 commits into
mainfrom
feat/IN-1212
Open

feat: migrate health score displays to v2 and implement Health Score v2 content spec IN-1212#2054
gaspergrom wants to merge 12 commits into
mainfrom
feat/IN-1212

Conversation

@gaspergrom

@gaspergrom gaspergrom commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Migrates every health score surface in Insights from the v1 backend (health_score_overview, category-average calculation) to v2 (project_insights, healthScoreV2 / healthLabel), and implements the Health Score v2 Content Spec end-to-end (spec: attachments/IN-1212/content-spec.md).
  • Rebuilds the project overview page's health card into a 3-column layout (Health Score ring, Impact, Lifecycle), adds a "Health breakdown" section with 3 real category cards (Maintainer Health, Security & Supply Chain, Development Activity) and per-category signal-row detail, and adds a new "Impact breakdown" section with 4 metric rows (transitive dependents, graph centrality, downloads, direct dependents).
  • Signal-row detail is real data, not a placeholder. crowd.dev PR #4448 (already deployed to production) exposes a new sub-signal pipe (project_insights_health_breakdown) that this PR's new health-score-breakdown.get.ts route consumes. An earlier revision of this PR omitted the per-signal rows because that data didn't exist yet — it does now, and the rows are live.
  • Every description on the page (lifecycle state, category summaries, per-signal rows, impact summary) is generated per-project from real signal values through a new config/health-breakdown-templates.ts module, following the bracket-pattern templates in the content spec. None of it is fixed text repeated across projects.
  • Migrates the v1→v2 label/threshold scheme (Excellent/Healthy/Fair/Concerning/Critical at 85/70/50/30) across the collection health pill, the project overview card, and the GitHub health-score badge endpoint. Fixes a pre-existing bug where "Fair" shared a color with "Concerning" on the collection pill (both now have distinct thresholds and dot colors).
  • Moves the archived-repos exclusion banner from inside/below the health-score card to above it, and rewords it — the health-score variant no longer claims exclusion from "Security & Best practices" (only v1 ever aggregated those together; v2 doesn't).
  • Removes dead v1 code: the lfxTrustScore array, getHealthScoreConfig, the TrustScoreConfig interface, the orphaned score-display.vue component, and the old 4-tab score-tabs.vue / score-details/ breakdown UI that the new health/impact breakdown sections replace.
  • Does not touch any v1 Tinybird pipe, datasource, or type — v1 code paths outside what's listed above are untouched.

Changes

File What changed
frontend/config/health-breakdown-templates.ts New. Per-project content generation for lifecycle, category, signal-row, and impact copy — replaces fabricated/fixed text.
frontend/config/trust-score.ts Removes v1 lfxTrustScore/getHealthScoreConfig/TrustScoreConfig. Adds v2 label/threshold config, impact label display, lifecycle label config.
frontend/server/api/project/[slug]/overview/health-score-v2.get.ts New. Fetches project_insights, returns health/impact/lifecycle scores and labels for the overview card.
frontend/server/api/project/[slug]/overview/health-score-breakdown.get.ts New. Fetches project_insights_health_breakdown — the sub-signal detail this PR's signal rows render.
frontend/server/api/project/[slug]/overview/health-score-impact.get.ts New. Fetches project_insights_impact_breakdown for the 4 impact metric rows.
frontend/server/api/badge/health-score.ts Switches from health_score_overview/overallScore to project_insights/healthLabel, and now derives the badge color from real v2 design tokens instead of a hardcoded value.
frontend/app/components/modules/project/components/overview/trust-score-v2.vue New. 3-column health/impact/lifecycle card with ring visualization, generated descriptions, and a documentation tooltip on the "HEALTH SCORE" label.
frontend/app/components/modules/project/components/overview/trust-score/health-score-ring.vue New. Thin wrapper reusing the existing lfx-chart gauge config pattern already live in security-score.vue.
frontend/app/components/modules/project/components/overview/health-breakdown-section.vue New. 3 category cards plus per-category signal-row detail (Maintainer responsiveness, Bus factor, Org diversity, etc.), driven by real sub-signal data.
frontend/app/components/modules/project/components/overview/health-breakdown/category-card.vue New. Single category card: progress bar, score, generated description.
frontend/app/components/modules/project/components/overview/impact-breakdown-section.vue New. 4 impact metric rows with percentile band badges and a status-driven error state.
frontend/app/components/modules/project/components/overview/impact-breakdown/metric-row.vue New. Single impact metric row, including a "Pending" state for signals still being computed (e.g. graph centrality).
frontend/app/components/modules/project/views/overview.vue Rebuilt to render the new 3-card layout (health/impact/lifecycle, health breakdown, impact breakdown) instead of the old trust-score + 4-tab score-tabs card. Banner moved above the health card.
frontend/app/components/modules/project/services/overview.api.service.ts Adds fetchHealthScoreV2, fetchHealthScoreBreakdown, fetchHealthScoreImpactBreakdown query functions.
frontend/app/components/modules/collection/components/details/collection-health-score-pill.vue Consumes real healthLabel when present (v2), with v1-threshold fallback for sparse rows. Fixes the Fair/Concerning color collision.
frontend/app/components/modules/collection/components/details/collection-project-item.vue Swaps the old 4-category hover popover for the v2 pill; isHealthScoreUnavailable now checks healthScoreV2 == null instead of any of the 4 v1 category scores.
frontend/app/components/modules/collection/components/details/collection-metrics-row.vue v1→v2 threshold migration for the collection-level average health label/dot color.
frontend/app/components/modules/collection/components/details/health-score-details.vue Deleted. Replaced by the v2 pill; the old 4-category popover is gone.
frontend/app/components/modules/project/components/overview/trust-score.vue, score-tabs.vue, score-details/* Deleted. Dead v1 overview UI, replaced by trust-score-v2.vue + the new breakdown sections.
frontend/app/components/modules/project/components/overview/trust-score/score-display.vue Deleted. Orphaned v1-only component.
frontend/app/components/modules/project/components/overview/trust-score/share-badge.vue Updated for v2 field names and repo-selection gating.
frontend/app/components/shared/components/repos-exclusion-footer.vue Reworded health-score variant copy; layout classes moved out (banner is now a sibling above the card, not nested inside it).
frontend/app/components/shared/components/health-score.vue Minor v2 field adjustments.
frontend/app/components/uikit/benchmarks/benchmark-icon.vue, benchmarks.scss Adds a no-data/circle variant used by the new signal rows.
frontend/app/components/uikit/chart/configs/gauge.chart.ts, ChartTypes.ts Adds graphOnly gauge mode used by health-score-ring.vue.
frontend/app/components/uikit/tag/* Adds a dashed tag type used for the impact breakdown's "Pending" state.
frontend/app/config/styles/colors.ts, frontend/tailwind.config.js Adds the health-fair color token (Tailwind safelist) needed by the new Fair threshold band.
frontend/types/overview/responses.types.ts Adds HealthScoreV2Results, ImpactBreakdownResults, HealthBreakdownResults types.
frontend/types/project.ts Adds v2 fields (healthScoreV2, healthLabel, lifecycleLabel, impactScore, impactLabel, category scores) to ProjectInsightsTinybird and ProjectInsights, additive alongside existing v1 fields.

JIRA

IN-1212 — Migrate health score displays to v2 backend + Health Score v2 Content Spec

Known gaps / follow-ups

Confirmed with the ticket owner as intentionally deferred, not blocking this PR:

  1. Two content-spec info banners are not implemented. The rescaled-category-coverage banner and the category-dropped/unavailable banner have no corresponding UI in this PR — no backend signal currently distinguishes those states from a normal low score.
  2. Impact label vocabulary mismatch. The content spec calls for 5 bands (Foundational/Major/Significant/Moderate/Limited); the deployed crowd.dev pipe only computes 4 (foundational/major/moderate/minor). This PR ships with the 4-band vocabulary the backend actually returns. Needs a spec-owner decision on whether to add a 5th band upstream or update the spec.
  3. 4 of 6 "undocumented content" items remain unresolved — UI elements with no Figma counterpart. 2 of the original 6 were resolved directly with design (Nuno) during this PR's development; the remaining 4 are still open.
  4. The documentation tooltip has no real target URL yet. The "HEALTH SCORE" label tooltip renders with placeholder explanatory copy; the "Learn more" link markup exists in trust-score-v2.vue but is commented out pending a real doc URL from design.

Deploy order

  1. crowd.dev PR #4448 — already deployed to production. Exposes project_insights_health_breakdown and the other v2 sub-signal pipes this PR's routes consume.
  2. This PR — safe to deploy now; its dependency is already live.

DB migrations

No DB migrations.

Test plan

  • pnpm test — 133/133 passing
  • pnpm tsc-check — clean
  • pnpm lint:fix — clean
  • QA'd via crowd-insights-qa — 7/7 acceptance criteria PASS (one badge-color drift found and fixed during QA)
  • Manually verify the project overview health card on a project with full signal data (ring, 3 category cards, signal rows, impact rows all populated)
  • Manually verify a project with sparse/missing signals — confirm "No data" states render instead of fabricated zeros, and the impact breakdown's Pending state shows for signals still being computed
  • Verify the collection health pill Fair/Concerning colors are now visually distinct
  • Verify the GitHub health-score badge endpoint returns the correct label and color for a few known projects
  • Verify the archived-repos banner renders above the health card (not inside it) and no longer mentions "Security & Best practices" in the health-score variant

Checklist

  • git commit --signoff -S on every commit authored in this PR (all 10 IN-1212 commits are signed and GPG-verified)
  • MIT license header on every new source file
  • PR diff < 1000 lines — currently ~3,076 lines (1,886 insertions / 1,190 deletions across 41 files); see note below
  • No unrelated changes bundled
  • Cross-repo impact documented (crowd.dev PR #4448, above)

Note on diff size: this PR is well over the 1000-line target. Two commits in the branch history (82edccb1, f868049c) are unrelated fixes/chores from other authors pulled in from maingit diff origin/main...HEAD already excludes them, so they don't add to the count above. The size is inherent to the scope: a full v1→v2 migration plus the Figma-driven breakdown redesign across 3 new sections, all consuming the same crowd.dev PR #4448 dependency. An earlier revision of this PR shipped only the score-card migration without the breakdown sections; product then confirmed the full redesign was in scope for this ticket, which is why this revision includes both.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings August 4, 2026 02:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates health-score read paths and UI surfaces from v1 calculations to Tinybird’s v2 fields while preserving v1 infrastructure.

Changes:

  • Adds v2 score types, configuration, API route, and query integration.
  • Updates collection, project overview, public API, and badge displays.
  • Retains v1 data paths for follow-up removal.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/types/project.ts Adds v2 project insight fields.
frontend/types/overview/responses.types.ts Defines the v2 overview response.
frontend/server/api/project/[slug]/overview/health-score-v2.get.ts Exposes v2 overview data.
frontend/server/api/badge/health-score.ts Sources badge labels from v2 data.
frontend/config/trust-score.ts Adds v2 label and badge configuration.
frontend/app/components/shared/types/tanstack.ts Adds the v2 query key.
frontend/app/components/shared/components/health-score.vue Renames OSI score tiers.
frontend/app/components/modules/project/views/overview.vue Integrates the new v2 score card.
frontend/app/components/modules/project/services/overview.api.service.ts Adds the v2 overview query.
frontend/app/components/modules/project/components/overview/trust-score-v2.vue Implements the v2 score card.
frontend/app/components/modules/collection/components/details/health-score-details.vue Rebuilds collection score details.
frontend/app/components/modules/collection/components/details/collection-project-item.vue Uses v2 collection score fields.
frontend/app/components/modules/collection/components/details/collection-health-score-pill.vue Supports backend-provided v2 labels.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/app/components/modules/project/components/overview/trust-score-v2.vue Outdated
Comment thread frontend/app/components/modules/project/views/overview.vue
Comment thread frontend/server/api/badge/health-score.ts Outdated
…er IN-1212

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 03:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

frontend/app/components/modules/project/views/overview.vue:17

  • The v2 response includes lifecycleLabel, but this view neither passes it to the new card nor renders it there. As a result, the project overview omits the lifecycle row promised by this migration even though the server already supplies the data. Pass the value through and display it in trust-score-v2.vue.
            :health-label="healthScoreV2Data?.healthLabel ?? null"

frontend/server/api/badge/health-score.ts:19

  • healthLabel is nullable, and the other new UI treats a null v2 score/label as unavailable. Here, passing null into getHealthScoreV2Config falls back to critical, so projects without enough data publish a misleading “Critical” badge. Handle the unavailable case explicitly before selecting the label/color (for example, return an unavailable badge or an appropriate HTTP response).
    const config = getHealthScoreV2Config(res.data[0].healthLabel);

frontend/app/components/modules/project/views/overview.vue:49

  • The v2 query is keyed only by project slug, so selecting a repository no longer hides or changes the displayed score. The prior card deliberately suppressed the project aggregate when selectedReposValues was non-empty; now the UI presents that aggregate as though it reflected the active repository selection. Keep the prior selected-repository treatment, or explicitly identify this as a project-wide score when a repository is selected.
const params = computed(() => ({
  projectSlug: route.params.slug as string,
}));

frontend/app/components/modules/project/views/overview.vue:15

  • This unconditional replacement drops the archived-project/all-repositories behavior from the previous card. The project store explicitly says archived projects/repositories are excluded from Health Score, but this component no longer reads isArchived; archived scopes now show either a stale score or the generic “not enough data” message instead of the archived empty state. Preserve the archived guard and explanatory empty state around the v2 card.
          <lfx-project-trust-score-v2

…iew components IN-1212

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 03:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (7)

frontend/app/components/modules/project/views/overview.vue:18

  • lifecycleLabel is returned by the new endpoint but is never passed to or rendered by this card. The PR describes the overview card as showing score, label, and lifecycle, so the current implementation omits a required v2 value.
          <lfx-project-trust-score-v2
            :health-score-v2="healthScoreV2Data?.healthScoreV2 ?? null"
            :health-label="healthScoreV2Data?.healthLabel ?? null"
            :status="healthScoreV2Status"

frontend/app/components/modules/collection/components/details/collection-project-item.vue:75

  • The collection health-score popover is removed here rather than migrated: health-score-details.vue is deleted and this is now a bare pill. That makes the overall-score/lifecycle details described in the PR inaccessible on the collection page. Restore the popover and back it with the v2 details UI.
        <lfx-collection-health-score-pill
          v-else
          :score="project.healthScoreV2 ?? 0"
          :health-label="project.healthLabel"
        />

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:12

  • Archived projects and all-archived repository selections no longer get the archive empty state; this component renders a score or the generic no-data message instead. The store explicitly states that archived projects/repositories are excluded from Health Score (project.store.ts:163-175), and the replaced component enforced that state. Restore the archived-state branch before rendering the score.
        <lfx-skeleton-state
          v-if="status === 'pending' || !isEmpty"

frontend/app/components/modules/project/views/overview.vue:49

  • Repository selection is dropped from the request here, while the repository switch remains active on the overview page. Selecting a repository therefore still shows the project-wide v2 score as though it reflected the selected repository; the previous card intentionally hid the aggregate and prompted users to select all repositories. Preserve that state handling, or clearly disable/label the score when a repository filter is active.
const params = computed(() => ({
  projectSlug: route.params.slug as string,
}));

frontend/config/trust-score.ts:84

  • A missing or unrecognized healthLabel is classified as Critical. Because the field is nullable and the collection pill explicitly handles sparse project_insights rows where the score exists but the label does not, both the overview and badge can falsely show a critical rating. Represent this as unavailable or derive a fallback from healthScoreV2 instead.
export const getHealthScoreV2Config = (label: string | null): HealthScoreV2Config => {
  if (label && healthScoreV2Config[label]) {
    return healthScoreV2Config[label];
  }
  return healthScoreV2Config.critical;

frontend/app/components/shared/components/health-score.vue:35

  • This component still receives a v1-derived score, so renaming its 40–59 v1 tier to the v2 term Fair can report a label that differs from the project's actual v2 healthLabel. Keep the v1 tier name until this surface is wired to v2 data.
    Fair

frontend/app/components/shared/components/health-score.vue:41

  • This component still receives a v1-derived score, so renaming its 20–39 v1 tier to the v2 term Concerning can report a label that differs from the project's actual v2 healthLabel. Keep the v1 tier name until this surface is wired to v2 data.
    Concerning

…N-1212

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 09:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (5)

frontend/app/components/modules/project/views/overview.vue:18

  • The PR description says the new overview card includes lifecycle, and the new endpoint returns lifecycleLabel, but this invocation drops that field and trust-score-v2.vue has no lifecycle prop or row. Pass and render healthScoreV2Data.lifecycleLabel, or update the declared scope if lifecycle is intentionally deferred.
          <lfx-project-trust-score-v2
            :health-score-v2="healthScoreV2Data?.healthScoreV2 ?? null"
            :health-label="healthScoreV2Data?.healthLabel ?? null"
            :status="healthScoreV2Status"

frontend/app/components/modules/project/views/overview.vue:45

  • The new card drops the archived-state guard that the previous overview card enforced. useProjectStore() still defines project/all-repositories archival as excluding Health Score, but this view now always renders the score card, so archived contexts can show a stale project score (or a generic “not enough data” message) instead of the archive empty state. Restore the isArchived, emptyStateTitle, and emptyStateDescription handling before rendering the v2 score.
const { hasSelectedArchivedRepos } = storeToRefs(useProjectStore());

frontend/app/components/modules/project/views/overview.vue:49

  • This query is now keyed only by project slug even though this overview is also mounted by the repository and repository-group routes. In those contexts the card therefore displays the project-wide v2 score as if it applied to the selected repository/group; the previous implementation explicitly suppressed the aggregate score when repositories were selected. Preserve that guard (or provide a repository-scoped v2 endpoint) so filtered views do not present the wrong scope.
const params = computed(() => ({
  projectSlug: route.params.slug as string,
}));

frontend/app/components/shared/components/health-score.vue:35

  • This caller still supplies a v1-derived score and has no authoritative v2 healthLabel, so changing the 40–59 bucket to “Fair” can claim a v2 category that the backend did not return. Keep the v1 terminology until the OSI endpoints expose v2 data; visual consistency should not override score semantics.
    Fair

frontend/app/components/shared/components/health-score.vue:41

  • This is the same semantic mismatch for the 20–39 bucket: the UI now presents the v2 label “Concerning” while the value remains v1-derived. Retain the v1 label until these list callers are migrated to the actual v2 score/label.
    Concerning

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 09:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (3)

frontend/app/components/modules/project/views/overview.vue:45

  • Dropping the archived-state refs removes the established Health Score exclusion behavior. Fully archived selections and archived projects now render the project-wide v2 score (or a generic no-data message) instead of the archived empty state, even though project.store.ts:161-175 defines archived projects/repositories as excluded from Health Score. Preserve isArchived and pass the existing empty-state title/description into the v2 card.
const { hasSelectedArchivedRepos } = storeToRefs(useProjectStore());

frontend/server/api/project/[slug]/overview/health-score-v2.get.ts:21

  • The new endpoint is described as returning all five v2 fields, but this destructuring drops lifecycleLabel, impactScore, and impactLabel. Consumers therefore cannot receive the documented response. Return all five fields and extend HealthScoreV2Results, or update the PR/API contract if the two-field response is intentional.
    const { healthScoreV2, healthLabel } = res.data[0];
    return { healthScoreV2, healthLabel };

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:79

  • The PR describes this card as including a lifecycle row, but the component accepts and renders only the health score and health label. As implemented, lifecycleLabel can never reach this UI. Pass it through the route/service/view and render the lifecycle state, or correct the PR description if this is intentionally out of scope.
const props = defineProps<{
  healthScoreV2: number | null;
  healthLabel: string | null;
  status: AsyncDataRequestStatus;
}>();

…olor IN-1212

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 10:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (4)

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:72

  • The share badge component switches between the project Health Score badge and the repository active-contributors badge via its optional isRepoSelected prop. Omitting it makes the prop default to false, so a repository-filtered overview now generates the project-level badge instead of the repository badge.
        <lfx-project-trust-score-share-badge />

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:114

  • The response contract allows a non-null score with a null label. In that state isEmpty is false, but this renders Unavailable <score>/100 with a negative dot; the collection pill explicitly falls back to score-derived v2 labeling for the same sparse-label case. Derive both the label and color from the score when the label is absent, or treat the pair consistently as unavailable.
const scoreLabel = computed(() => getHealthScoreV2Config(props.healthLabel).label);

const scoreColorClass = computed(() => {
  const label = props.healthLabel;
  if (label === 'excellent' || label === 'healthy') return 'bg-positive-500';
  if (label === 'fair' || label === 'concerning') return 'bg-health-concerning';
  return 'bg-negative-500';

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:69

  • This guard no longer excludes archived scopes, so the Health Score share badge is displayed alongside the archived empty state even though archived repositories/projects are excluded from Health Score. Keep the previous !isArchived guard.
        v-if="!isEmpty && status === 'success'"

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:12

  • This route fetches only by project slug, so the returned score is always the whole-project aggregate. The new condition still renders that value when a repository filter is active (and when the selected scope is archived), even though the notice says to select all repositories and archived repositories are excluded from Health Score. Restore the previous scope guards so an aggregate score is not presented as belonging to the current filtered/archived view.

This issue also appears in the following locations of the same file:

  • line 69
  • line 72
  • line 108
        <lfx-skeleton-state
          v-if="status === 'pending' || !isEmpty"

…ge correctly IN-1212

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 11:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (2)

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:13

  • The aggregate project score is still rendered when a repository is selected. This query never includes repository parameters, so users see the project-wide value directly above the instruction to select “All repositories”; the prior implementation suppressed that value in repo scope. Gate the score display on !isRepoSelected.
            v-if="status === 'pending' || !isEmpty"

frontend/app/components/shared/components/health-score.vue:33

  • Renaming this tier to the v2 “Fair” label without changing its variation leaves it blue, while every other v2 mapping renders both fair and concerning with the warning/amber color (trust-score-v2.vue:119, collection-health-score-pill.vue:61, and trust-score.ts:75-76). Use the warning variation so the same v2 label does not change meaning across surfaces.
    Fair

…IN-1212

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 14:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.

Suppressed comments (6)

frontend/app/components/modules/project/views/overview.vue:20

  • The current PR description explicitly limits this card to healthScoreV2/healthLabel and says the Lifecycle + Health + Impact redesign is follow-up work. Passing these fields—and adding the two breakdown cards below—reintroduces the broader redesign that the previous review response said was removed. Either remove this out-of-scope UI/data path or update the acceptance criteria and split/re-review the expanded feature.
            :impact-score="healthScoreV2Data?.impactScore ?? null"
            :impact-label="healthScoreV2Data?.impactLabel ?? null"
            :lifecycle-label="healthScoreV2Data?.lifecycleLabel ?? null"

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:9

  • fetchHealthScoreV2 sends only the project slug, so this content is always the all-repositories aggregate. This condition still renders that score, impact, lifecycle, and the breakdown cards when a repository is selected, directly above a notice saying the aggregate requires “All repositories.” Gate all aggregate sections on no repository selection, or add a genuinely repository-scoped backend query.
        v-if="status === 'pending' || !isEmpty"

frontend/server/api/project/[slug]/overview/health-score-v2.get.ts:28

  • These three fields are not present in project_insights_copy_ds and are not selected by the upstream project_insights.pipe; they exist only in the repo-level health-score pipeline. They therefore resolve to undefined, so every project health-breakdown category displays “No data.” Remove this breakdown or add and deploy an actual project-level rollup before consuming it.
      maintainerHealthScoreV2,
      securitySupplyChainScoreV2,
      developmentActivityScoreV2,

frontend/app/components/modules/collection/components/details/collection-health-score-pill.vue:48

  • The fallback now derives a v2 label from a v2 score, but it retains v1 cutoffs (80/60/40/20). The upstream v2 bands are 85/70/50/30, so sparse rows are misclassified (for example, 80 becomes Excellent instead of Healthy), and the dot cutoffs disagree too.
// Prefers the API's real healthLabel (v2) when present; falls back to deriving the label from
// the score for sparse rows where the pipe didn't return a label.
const healthScoreLabel = computed(() => {
  if (props.healthLabel) {
    return props.healthLabel.charAt(0).toUpperCase() + props.healthLabel.slice(1);

frontend/app/components/shared/components/health-score.vue:35

  • This component still receives a v1 score and applies v1 thresholds, so renaming the buckets does not produce a v2 classification. The v2 cutoffs differ (85/70/50/30), meaning an OSI item can display a v2 term that its actual v2 backend label would not have. Keep the v1 terminology until these callers receive v2 data.
    Fair

frontend/app/components/modules/project/views/overview.vue:44

  • This condition is true when the health query is in error, and the independent impact query's status is discarded entirely. A health failure produces an empty card, while an impact 404/500 leaves a header-only breakdown with no error or empty state. Track both query statuses and render the card only for success, with an explicit pending/error state if the breakdown remains in scope.
          v-if="healthScoreV2Status !== 'pending' && !isArchived"

…eakdown styling IN-1212

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 17:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (5)

frontend/app/components/modules/project/views/overview.vue:31

  • This card is mounted after a failed or empty health-score response, but health-breakdown-section.vue renders nothing when healthScoreV2 is null. That leaves an empty padded card below the main error/empty state. Gate the wrapper on an available score (or render an explicit state inside the section).
          v-if="healthScoreV2Status !== 'pending' && !isArchived"

frontend/app/components/modules/project/components/overview/impact-breakdown-section.vue:79

  • The breakdown endpoint is independent of the overall Osprey impactScore, so it can contain downloads/dependent metrics when that summary score is null. Keying isEmpty to impactScore hides all available rows and also prevents the per-field “No data” states from rendering. Base visibility on the breakdown response/status and keep the summary chip optional.
const isEmpty = computed(() => props.impactScore === null);

frontend/app/components/modules/project/components/overview/trust-score-v2.vue:142

  • isEmpty suppresses the entire three-column layout whenever only healthScoreV2 is null. Impact and lifecycle are independently computed nullable fields, so valid values for those columns are lost for projects without an overall health score. Render each column’s unavailable state independently, or treat the card as empty only when all three values are absent.
const isEmpty = computed(() => props.healthScoreV2 === null);

frontend/types/project.ts:140

  • This shared Tinybird type is also used for project_repo_insights in frontend/server/api/collection/[slug]/project-repos.ts:111, but that pipe does not select these three category fields. Declaring them as always present makes that route’s runtime response disagree with its type. Use an endpoint-specific type, make the fields optional and normalize the overview response, or add them to the upstream pipe.
  maintainerHealthScoreV2: number | null;
  securitySupplyChainScoreV2: number | null;
  developmentActivityScoreV2: number | null;

frontend/app/components/modules/project/views/overview.vue:44

  • This gate watches only the health-score query. If that request finishes first, the Impact card mounts while its own data is still pending and shows no loading state; the inverse completion order can likewise leave the summary unavailable. Wait for both independently fetched responses before mounting the card.
          v-if="healthScoreV2Status !== 'pending' && !isArchived"

@gaspergrom
gaspergrom requested review from emlimlf and epipav August 5, 2026 10:29
Copilot AI review requested due to automatic review settings August 6, 2026 12:41
@gaspergrom gaspergrom changed the title feat: migrate health score display to v2 feat: migrate health score displays to v2 and implement Health Score v2 content spec IN-1212 Aug 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated no new comments.

Suppressed comments (10)

frontend/config/health-breakdown-templates.ts:103

  • Excellent projects can enter this branch and receive text saying a weakness is “keeping the score from Excellent,” contradicting the displayed Excellent label. Handle Excellent separately and reserve the gap wording for Healthy.
    frontend/app/components/modules/project/views/overview.vue:44
  • On a health-score request failure this card still renders with impactScore=null, so it tells users the project publishes no tracked packages even though the actual state is a fetch error. Only render this dependent section after the health-score query succeeds.
          v-if="healthScoreV2Status !== 'pending' && !isArchived"

frontend/app/components/modules/project/components/overview/impact-breakdown-section.vue:48

  • A null centrality value is a no-data state, not a pending computation. The upstream rollout explicitly reports 0% coverage and says null should display “No data”; this condition instead always renders “Pending” and promises completion by a weekly job that does not populate centrality. Render the normal null state until the API exposes a distinct pending signal.
          :description="getGraphCentralityDescription(data.centrality, data.centrality === null)"
          :value="data.centrality"
          :band="data.centralityBand"
          :is-pending="data.centrality === null"

frontend/server/api/project/[slug]/overview/health-score-breakdown.get.ts:12

  • This route depends on project_insights_health_breakdown, but that endpoint is introduced by crowd.dev PR #4448, which is still open and behind its base branch. The deploy section currently says all backend dependencies are merged and only lists #4438/#4439. Please link #4448 and require it to merge before this route ships; a manually deployed but unmerged Tinybird contract can disappear on a later deployment.
    frontend/app/components/modules/project/views/overview.vue:95
  • The health-breakdown query discards its status/error. While it is pending—or permanently after failure—the component receives signals=null; getCategoryDescription then claims each scored category was dropped from the composite, and all signal rows disappear without an error. Pass the query status into the section and render loading/error states separately from genuine missing signal data.
const { data: healthBreakdownData, suspense: healthBreakdownSuspense } =
  OVERVIEW_API_SERVICE.fetchHealthScoreBreakdown(params);

frontend/config/health-breakdown-templates.ts:29

  • These descriptions assert signals the lifecycle calculation does not establish. active is the backend fallback and does not guarantee responsive maintainers, regular releases, or healthy triage; stable allows up to 49 open issues and does not test maintainer reachability. Use wording limited to the actual lifecycle classification criteria.

This issue also appears on line 99 of the same file.
frontend/config/health-breakdown-templates.ts:377

  • The upstream contract documents all three vulnerability counts as null when no vulnerability rows exist. Coalescing those nulls to zero makes an unindexed project receive a positive “No open vulnerabilities” result. Return a no-data row when every count is null before evaluating severity totals.
    frontend/app/components/modules/project/components/overview/health-breakdown/category-card.vue:8
  • This introduces a raw <button> even though the repository rule requires lfx-button/lfx-icon-button whenever a uikit equivalent exists (.claude/rules/always-use-uikit.md:9-14). Please implement the selectable card through the uikit button API rather than bypassing the design-system component.
  <button
    type="button"
    class="flex-1 min-w-0 flex flex-col gap-4 items-start p-4 rounded-md text-left transition-colors"

frontend/app/components/shared/components/health-score.vue:24

  • This component still receives a v1 score, but now classifies it with v2 thresholds and labels. That guarantees mislabeled OSI rows—for example, a v1 score of 82 changes from Excellent to Healthy without the underlying calculation changing. Keep the v1 tiers until callers supply healthScoreV2, or migrate the data source in the same change.
    v-else-if="props.score >= 85"
    variation="positive-solid"
  >
    Excellent
  </lfx-tag>

frontend/app/components/modules/project/views/overview.vue:30

  • On a health-score request failure this condition still mounts the breakdown card with all-null props; the child renders nothing, leaving an empty card below the real error state. Gate the dependent section on a successful health-score response.

This issue also appears on line 44 of the same file.

          v-if="healthScoreV2Status !== 'pending' && !isArchived"

Copilot AI review requested due to automatic review settings August 7, 2026 08:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 1 comment.

Suppressed comments (10)

frontend/app/components/modules/project/views/overview.vue:95

  • The breakdown query's status is discarded. While it is loading—or after a 404/500—the section receives signals = null, describes scored categories as “dropped,” and removes all signal rows, making an upstream failure look like genuine no-data. Preserve the query status and render loading/error states separately from a successful sparse response.
const { data: healthBreakdownData, suspense: healthBreakdownSuspense } =
  OVERVIEW_API_SERVICE.fetchHealthScoreBreakdown(params);

frontend/app/components/modules/project/views/overview.vue:44

  • On a primary v2 query failure this condition still renders the Impact section with impactScore = null, so users are incorrectly told that the project publishes no tracked packages. Gate this card on a successful primary query rather than treating an API failure as valid empty data.
          v-if="healthScoreV2Status !== 'pending' && !isArchived"

frontend/config/health-breakdown-templates.ts:173

  • A category score reaching the success band does not prove there are zero CVEs or that every security practice is enabled; the nullable vulnerability counts and practice flags can contradict this unconditional sentence, and the signal rows below will then show both claims at once. Build this summary from the actual fields (and omit claims whose inputs are unavailable) rather than inferring all facts from the aggregate band.
    frontend/config/health-breakdown-templates.ts:377
  • All three vulnerability counts are nullable, and the upstream pipe documents them as NULL when there are no vulnerability rows, but nulls are converted to zero here. A project with no vulnerability data therefore renders a positive “No open vulnerabilities” row. Return no-data whenever the inputs needed to establish a clean result are missing.
    frontend/config/health-breakdown-templates.ts:316
  • This declares the whole responsiveness signal unavailable whenever the issue median is null, although the upstream response exposes medianPrResponseS independently and documents that either median can be NULL. When PR response data exists, the “No issue or PR response data” message is false. Incorporate the PR median as a fallback or combine both medians according to the content spec.
    frontend/app/components/modules/project/views/overview.vue:30
  • This card is also rendered when the primary v2 query is in error, because every non-pending state passes this condition. In that case all props fall back to null, leaving an empty Health breakdown card instead of preserving the Health-score error state. Only render it after a successful primary query.

This issue also appears in the following locations of the same file:

  • line 44
  • line 94
          v-if="healthScoreV2Status !== 'pending' && !isArchived"

frontend/app/components/shared/components/health-score.vue:10

  • This explicitly leaves the two OSI surfaces on a v1 score while applying v2 thresholds and labels. That can reclassify the same v1 value under a scale it was not computed for, and it contradicts the PR's claim that every health-score surface moved to healthScoreV2. Wire these callers to v2 data in this PR, or retain the v1 labeling until that migration is delivered and scope the PR description accordingly.
  Labels below use the v2 health score convention (excellent/healthy/fair/concerning/critical,
  see collection-health-score-pill.vue) for consistency with the rest of the app. The score
  itself is still v1 here — osi-list-projects.vue and osi-list-collections.vue (this component's
  only callers) read healthScore from projects_list/collections_oss_index, neither of which
  carries v2 fields. Wiring these lists to v2 needs a data-layer follow-up.

frontend/config/health-breakdown-templates.ts:103

  • excellent and healthy share this branch, so an Excellent project with more than one available category is told that a remaining gap is “keeping the score from Excellent.” Handle Excellent separately so the generated summary cannot contradict the displayed label.

This issue also appears in the following locations of the same file:

  • line 173
  • line 309
  • line 374
    frontend/app/components/modules/project/components/overview/health-breakdown/category-card.vue:8
  • This introduces a raw <button> even though the repository explicitly requires lfx-button or lfx-icon-button for button controls (.claude/rules/always-use-uikit.md:9-14). Using the uikit control preserves the shared interaction, disabled, and accessibility behavior while allowing these card styles through attributes/slots.
  <button
    type="button"
    class="flex-1 min-w-0 flex flex-col gap-4 items-start p-4 rounded-md text-left transition-colors"

frontend/types/overview/responses.types.ts:91

  • This response type models the upstream *Available, isGerrit, and isExcluded columns as booleans, but PR #4448 declares and returns them as nullable Tinybird UInt8 values. fetchFromTinybird performs no conversion, so this API actually returns 0 | 1 | null while advertising booleans. Match the wire types (as the security flags already do) or normalize every flag in the route before returning it.

Migrates the health score card, breakdown tabs, share badge, collection
health pill, and impact breakdown from the v1 backend to v2, and
implements the Health Score v2 content spec end-to-end:

- Real per-project generated copy (lifecycle description, category
  descriptions, signal-row descriptions, impact summary) replacing
  fixed/fabricated text that was previously identical across all
  projects, via a new config/health-breakdown-templates.ts module.
- New health-breakdown-section.vue signal rows per category
  (Maintainer/Security/Development), driven by the new
  health-score-breakdown server route consuming crowd.dev's newly
  exposed sub-signal detail.
- Impact breakdown badges, per-signal descriptions, and a Pending state
  for signals still being computed (e.g. graph centrality).
- v1 -> v2 label/threshold migration (Excellent/Healthy/Fair/Concerning/
  Critical @ 85/70/50/30) across trust-score.ts, collection health pill,
  and the GitHub badge endpoint, removing the dead v1 lfxTrustScore
  config and its orphaned score-display.vue consumer.
- Archived-repos exclusion banner moved above the health-score card and
  reworded (design feedback), with a documentation tooltip on the
  Health Score label pending a real doc link.

Depends on the crowd.dev CM-IN-1212 pipe chain (already deployed to
production).

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
@gaspergrom
gaspergrom requested review from Copilot and removed request for emlimlf August 7, 2026 10:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 1 comment.

Suppressed comments (11)

frontend/app/components/modules/project/views/overview.vue:44

  • This condition renders the Impact breakdown after the overview request fails. The missing response is converted to impactScore = null, so the card incorrectly claims the project publishes no tracked packages. Gate the card on a successful overview response.
        <lfx-card
          v-if="healthScoreV2Status !== 'pending' && !isArchived"

frontend/config/health-breakdown-templates.ts:28

  • active and stable do not guarantee this copy. The deployed lifecycle decision tree uses active as the fallback, while stable allows up to 49 open issues and non-critical vulnerabilities; project rollup is also best-state-wins. These descriptions can therefore assert responsiveness, regular releases, or no actionable issues that the signals do not establish. Generate neutral/state-accurate copy or derive each claim from the corresponding raw signal.
    frontend/config/health-breakdown-templates.ts:101
  • For an excellent project with non-identical category percentages, this branch says a category is “keeping the score from Excellent” even though the score is already Excellent. Handle excellent separately so the generated summary does not contradict the displayed label.
    frontend/config/health-breakdown-templates.ts:314
  • This checks only the issue median. The deployed v2 calculation averages PR and issue medians (falling back to whichever exists), and for non-Gerrit repositories with neither response stream the absence is a real negative score, not blocked data. As written, PR-only/Gerrit projects and unresponsive GitHub projects are both mislabeled “No data.” Mirror the upstream combined median and negative empty case.
    frontend/app/components/modules/project/views/overview.vue:95
  • The breakdown query status is discarded. While it is loading—or permanently after an error—signals is null; the component then describes scored categories as unavailable/dropped and silently removes all signal rows. Pass the query status/error through and render a loading or error state before generating category copy.
const { data: healthBreakdownData, suspense: healthBreakdownSuspense } =
  OVERVIEW_API_SERVICE.fetchHealthScoreBreakdown(params);

frontend/app/components/modules/project/views/overview.vue:30

  • This condition also renders the Health breakdown after the overview request fails. Because all response props then default to null, users see a low-coverage/unavailable-data explanation for what is actually a request error. Only render this data-dependent card after a successful overview response.

This issue also appears on line 43 of the same file.

        <lfx-card
          v-if="healthScoreV2Status !== 'pending' && !isArchived"

frontend/app/components/shared/components/health-score.vue:10

  • This component explicitly still receives v1 scores, but the change applies v2 thresholds and labels to them. That produces labels that match neither the v1 contract nor actual v2 data, and contradicts the PR’s claim that every health-score surface was migrated. Feed these callers healthScoreV2/healthLabel, or retain the v1 mapping until those list pipes are migrated.
  Labels below use the v2 health score convention (excellent/healthy/fair/concerning/critical,
  see collection-health-score-pill.vue) for consistency with the rest of the app. The score
  itself is still v1 here — osi-list-projects.vue and osi-list-collections.vue (this component's
  only callers) read healthScore from projects_list/collections_oss_index, neither of which
  carries v2 fields. Wiring these lists to v2 needs a data-layer follow-up.

frontend/app/components/modules/project/components/overview/health-breakdown/category-card.vue:14

  • This introduces a raw <button> even though the repository’s UI rule requires the existing lfx-button/lfx-icon-button components for buttons. Using the uikit component preserves shared interaction, focus, disabled, and styling behavior.
  <button
    type="button"
    class="flex-1 min-w-0 flex flex-col gap-4 items-start p-4 rounded-md text-left transition-colors"
    :class="
      props.selected
        ? 'c-card !border-transparent !shadow-md !rounded-md'
        : 'bg-transparent border border-transparent hover:bg-white/60'
    "
    @click="emit('select')"

frontend/app/components/modules/project/components/overview/trust-score/share-badge.vue:10

  • When a repository is selected, this image is the active-contributors badge, but its alternative text still announces a Health Score badge. Bind the alt text to the same selection condition as src.
      <img
        :src="props.isRepoSelected ? repoBadgeUrl : badgeUrl"
        alt="Health Score Badge"

frontend/types/overview/responses.types.ts:91

  • This interface does not match the raw Tinybird payload returned by the new route: *Available, isGerrit, and isExcluded are nullable UInt8 values (0/1), not booleans, and branchProtectionRequiredReviews is an Int32 count that can exceed 1. Either normalize the route response or model the wire values accurately; otherwise consumers are type-checking against values that never arrive at runtime.
    frontend/config/health-breakdown-templates.ts:172
  • A successful aggregate category band does not imply zero vulnerabilities: the upstream score can remain above this threshold while openCriticals, openHighs, or openModerates is nonzero. This unconditional sentence can display “No open CVEs” beside a real vulnerability count. Build that clause from the counts instead of the aggregate band.

Comment on lines +30 to +33
fetchHealthScoreV2(params: ComputedRef<{ projectSlug: string }>) {
const queryKey = computed(() => [TanstackKey.HEALTH_SCORE_V2, params.value.projectSlug]);
const queryFn: QueryFunction<HealthScoreV2Results> = async () =>
await $fetch(`/api/project/${params.value.projectSlug}/overview/health-score-v2`);
Copilot AI review requested due to automatic review settings August 7, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 2 comments.

Suppressed comments (9)

frontend/config/health-breakdown-templates.ts:385

  • openModerates contains only moderate-severity findings, but this sentence labels the count as “medium or low.” That overstates what the returned value represents.
    frontend/app/components/modules/project/views/overview.vue:95
  • The breakdown query status is discarded. If this request fails while the score request succeeds, signals becomes null and every scored category is rendered with an unavailable/dropped description, with no error shown. Preserve the query status and render an error state instead of treating transport failure as domain no-data.
const { data: healthBreakdownData, suspense: healthBreakdownSuspense } =
  OVERVIEW_API_SERVICE.fetchHealthScoreBreakdown(params);

frontend/app/components/modules/project/components/overview/health-breakdown-section.vue:37

  • This asserts a specific coverage reason whenever the overall score is null, but the PR’s known gap states the backend cannot distinguish low coverage from other unavailable states such as no indexed repositories. This will show fabricated diagnostics for those projects; use neutral unavailable copy until an explicit reason is returned.
      <span>Health Score unavailable. All three categories have less than 40% signal coverage for this project.</span>

frontend/config/health-breakdown-templates.ts:231

  • A null category score or missing breakdown response is not proof that the category was dropped for signal coverage, yet this path delegates to copy that states exactly that. The backend currently provides no reason code, so projects with no indexed repos—and transient request failures—receive a false explanation. Use neutral copy or add an explicit availability reason before claiming a category was dropped.
    frontend/config/health-breakdown-templates.ts:314
  • Checking only the issue median misclassifies Gerrit projects (which have no issue tracker by design) and projects with PR-only response data, even though the backend marks responsiveness available and scores the PR median. Compute the effective PR/issue response time using the backend’s Gerrit/coalescing rules; when an available non-Gerrit project has neither median, that is a negative signal rather than no-data.
    frontend/app/components/modules/project/components/overview/impact-breakdown-section.vue:48
  • A null centrality value is the API’s no-data state, not a computation-status signal; the upstream pipe explicitly preserves null for missing values and says consumers should render “no data.” Treating every null as Pending promises a weekly completion that may never occur. Render no-data unless the API adds an explicit pending status.
          :description="getGraphCentralityDescription(data.centrality, data.centrality === null)"
          :value="data.centrality"
          :band="data.centralityBand"
          :is-pending="data.centrality === null"

frontend/app/components/modules/project/components/overview/health-breakdown/category-card.vue:8

  • This introduces a raw <button> even though the repository’s always-use-uikit.md rule requires lfx-button or lfx-icon-button whenever a button is needed. Use the uikit button and preserve these card classes/slot contents so interaction styling and accessibility behavior remain centralized.
  <button
    type="button"
    class="flex-1 min-w-0 flex flex-col gap-4 items-start p-4 rounded-md text-left transition-colors"

frontend/app/components/shared/components/health-score.vue:20

  • These callers still pass the legacy v1 healthScore from projects_list and collections_oss_index, as the new comment acknowledges. Applying v2 thresholds to a v1 score does not migrate this surface and can display a label that disagrees with the project’s real v2 label. Wire v2 fields through these list APIs, or retain the v1 bands until that migration is available.
    v-else-if="props.score >= 85"

frontend/app/components/modules/collection/components/details/collection-metrics-row.vue:109

  • avgHealthScore is still computed upstream by averaging the legacy healthScore, so applying the v2 85/70/50/30 bands relabels a v1 aggregate rather than migrating the metric. This can disagree with the v2 labels shown in the project rows. The aggregate pipe must average healthScoreV2 (with its null semantics), or this UI must keep the v1 bands.
  if (score >= 85) return 'Excellent';
  if (score >= 70) return 'Healthy';
  if (score >= 50) return 'Fair';
  if (score >= 30) return 'Concerning';

Comment on lines 78 to 80
const params = computed(() => ({
projectSlug: route.params.slug as string,
repos: selectedReposValues.value,
}));
Comment on lines +372 to +375
export const getOpenVulnRow = (signals: HealthBreakdownResults): SignalRow => {
const criticals = signals.openCriticals ?? 0;
const highs = signals.openHighs ?? 0;
const moderates = signals.openModerates ?? 0;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants