From c4ac8210c75d388e36d6af94ba5509de586714a4 Mon Sep 17 00:00:00 2001 From: Torgny Bjers Date: Mon, 6 Jul 2026 23:10:24 -0400 Subject: [PATCH 1/2] chore: add badge-category-selection plan file --- docs/plans/badge-category-selection-plan.md | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/plans/badge-category-selection-plan.md diff --git a/docs/plans/badge-category-selection-plan.md b/docs/plans/badge-category-selection-plan.md new file mode 100644 index 0000000..a135be8 --- /dev/null +++ b/docs/plans/badge-category-selection-plan.md @@ -0,0 +1,108 @@ +# Add category selection to the "Create status badge" modal + +## Context + +Coverage/complexity/duplication data can already be tracked per **category** — a free-form +label (`coverage_runs.category` / `coverage_daily.category`, migration `0003_categories.sql`) +that partitions independent report series within one project (e.g. "backend" vs "frontend"). +The main project dashboard already renders one trend card per discovered category +(`dashboard/src/routes/[owner]/[repo]/+page.svelte`), and the PR-check/baseline endpoint +already reads a `?category=` query param (`src/routes/baseline.ts`). + +The "Create status badge" modal (`BadgeModal.svelte`) and its backend +(`src/routes/badge.ts`) were never updated for this: the modal only lets a user pick a +**metric** (coverage, complexity, duplication, etc.) and always produces a badge URL with no +category, and the badge endpoint always reads the `'default'` category regardless of what's +requested. Users with categorized projects can't get a status badge for anything but the +implicit default series. This plan adds a category picker to the modal and wires it through +to the badge endpoint, including the generated badge's label, for all metric types. + +## Backend: `src/routes/badge.ts` + +`getLatestCoverage(db, projectId, branch, category)` already accepts a `category` param +(default `'default'`) — `src/lib/db.ts:274`. `badge.ts` just never passes it through. Change: + +- Read `const category = c.req.query('category') ?? 'default';` (same pattern as + `src/routes/api.ts:35` and `baseline.ts`). +- Pass it into `getLatestCoverage(c.env.DB, project.id, project.default_branch, category)`. +- Include the category in the returned `label` when it isn't the default, so the badge itself + communicates which series it's showing: + ```ts + label: category === 'default' ? metricName : `${category} ${metricName}`, + ``` +- No new validation needed — an unknown/malformed category simply yields no matching row and + `getLatestCoverage` returns `null`, which already 404s (consistent with how `baseline.ts`/ + `api.ts` treat category as an unvalidated read-side filter). + +This applies uniformly to every metric type (`coverage`, `branch_coverage`, `complexity`, +`cognitive`, `duplication`, `maintainability`) since category is orthogonal to metric — one +category row carries all metric columns. + +## Frontend: `dashboard/src/lib/components/BadgeModal.svelte` + +1. Add a `defaultBranch: string` prop (needed to query categories for the right branch; the + badge endpoint itself always uses the project's default branch, so the picker must match). +2. Add state: + - `categories = $state(['default'])` + - `selectedCategory = $state('default')` +3. Fetch categories whenever the selected metric changes, reusing the existing helper + `fetchTrendByCategory(owner, repo, selectedMetric, defaultBranch, 1)` from + `dashboard/src/lib/api.ts:25` (already used by the project page to discover categories) — + no new endpoint required. On response, take `result.categories.map(c => c.category)`; + fall back to `['default']` on an empty list or fetch error. If the current + `selectedCategory` isn't in the new list, reset it to `categories[0] ?? 'default'`. + - Doing this per-metric (rather than once) is intentional: a category with no data for the + currently selected metric wouldn't produce a working badge anyway (`getLatestCoverage` + would return a null value for that column), so scoping the picker to + metric+category combinations that actually have data avoids offering dead combinations. +4. Add a second `