From 508c7530e9aebc603f1bfb82e6cf9d88cdfafd6a Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Thu, 14 May 2026 16:52:19 +0000 Subject: [PATCH] =?UTF-8?q?fix(web):=20Hotfix=20#8=20=E2=80=94=20L-015=20l?= =?UTF-8?q?ine-grep=20guard=20+=20CodeQL=20formatStatus=20orphan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two separate issues caught after Phase 5 push: ═════════════════════════ ISSUE 1: L-015 CI GUARD ═════════════════════════ The Frontend Build job on commit 868f1c25 (sidebar maintainer attribution) failed with: ::error::L-015 regression: target="_blank" without rel="noopener noreferrer": web/src/components/Layout.tsx:297: target="_blank" Root cause: the bundle-8-L-015-target-blank-rel-noopener.sh guard uses LINE-BASED grep — it greps each line for `target="_blank"` then filters lines containing `noopener noreferrer`. My sidebar attribution split those across two lines (target= on 297, rel= on 298), so the line with target= never had noopener visible to the line-grep filter and the guard fired. Worth noting: a Haiku-generated recommendation on the failing run claimed "the code already has the correct rel attribute, re-run the CI job." That recommendation was wrong — I verified the failure reproduces locally. Haiku also invented a "FormField React.Children.only" error that doesn't exist (all 7 FormField tests pass locally). Ignored both. Fix: migrate the sidebar attribution from a bare to . ExternalLink (web/src/components/ ExternalLink.tsx) is the canonical chokepoint Bundle-8 shipped exactly for this case — it always emits `rel="noopener noreferrer"` and is allowlisted by the L-015 guard. Trade-off: lost the rel="me" identity- claim hint LinkedIn uses (not load-bearing — LinkedIn's verification flow doesn't depend on it); gained the CI gate. Documented in the edit-site comment. ═════════════════ ISSUE 2: CODEQL js/unused-local-variable #35 ═════════════ CodeQL flagged web/src/pages/DashboardPage.tsx:33 — `formatStatus` is defined but never used. Root cause: Phase 4 (commit 9ce2d8ca) extracted the four chart panels into pages/dashboard/charts.tsx, which also moved formatStatus + its callers. The local definition in DashboardPage stayed behind as dead code. CodeQL's first detection at 868f1c25 is just when the alert was raised — the orphan dates from 9ce2d8ca. Fix: delete the local formatStatus line, leaving a comment that points to its new home (pages/dashboard/charts.tsx). ══════════════════════════════ VERIFICATION ════════════════════════════════ • npx tsc --noEmit — exits 0 • All 33 CI guards pass locally (bash scripts/ci-guards/*.sh loop — bundle-8-L-015 now green; no-unbound-label still at baseline 132) • Layout 7/7 + DashboardPage 4/4 = 11/11 green • npx vite build — ✓ in 3.30s • grep target="_blank" web/src/components/Layout.tsx → only matches the explanatory comment, not actual JSX • grep formatStatus web/src/pages/DashboardPage.tsx → only matches the explanatory comment, not actual code Next CI run on master should land green. --- web/src/components/Layout.tsx | 14 ++++++++++---- web/src/pages/DashboardPage.tsx | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/web/src/components/Layout.tsx b/web/src/components/Layout.tsx index 6ea0559..be33975 100644 --- a/web/src/components/Layout.tsx +++ b/web/src/components/Layout.tsx @@ -51,6 +51,7 @@ import { } from 'lucide-react'; import type { LucideIcon } from 'lucide-react'; import { useAuth } from './AuthProvider'; +import { ExternalLink } from './ExternalLink'; import logo from '../assets/certctl-logo.png'; // ----------------------------------------------------------------------------- @@ -289,18 +290,23 @@ export default function Layout() { the LinkedIn link (the same href + rel="me noopener" pattern the landing page uses). Single-maintainer OSS standard (Cal.com, Plausible, Beekeeper Studio do the same). */} + {/* Maintainer attribution row. The Bundle-8 L-015 CI guard line-greps + for `target="_blank"` without `rel="noopener noreferrer"` on the + SAME LINE — splitting target + rel across lines (as the prior + bare did) tripped the guard. ExternalLink is the canonical + chokepoint that the guard allowlists. We lose the rel="me" hint + (LinkedIn's identity-claim signal, not load-bearing), but gain + the CI gate. */}
Built and maintained by{' '} - Shankar - +
diff --git a/web/src/pages/DashboardPage.tsx b/web/src/pages/DashboardPage.tsx index 14cdae9..c75e1b0 100644 --- a/web/src/pages/DashboardPage.tsx +++ b/web/src/pages/DashboardPage.tsx @@ -29,8 +29,9 @@ import { // every dashboard load after the operator dismisses it once. const OnboardingWizard = lazy(() => import('./OnboardingWizard')); -// Convert PascalCase status like "RenewalInProgress" to "Renewal In Progress" -const formatStatus = (s: string) => s.replace(/([a-z])([A-Z])/g, '$1 $2'); +// formatStatus moved to pages/dashboard/charts.tsx in Phase 4 alongside +// the memoized chart panels that use it; deleted from here in Hotfix #8 +// to close CodeQL js/unused-local-variable alert #35. const STATUS_COLORS: Record = { Active: '#10b981',