fix(web): Hotfix #8 — L-015 line-grep guard + CodeQL formatStatus orphan

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 <a target="_blank">
to <ExternalLink href={...}>. 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.
This commit is contained in:
shankar0123
2026-05-14 16:52:19 +00:00
parent c9f932be65
commit 508c7530e9
2 changed files with 13 additions and 6 deletions
+10 -4
View File
@@ -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 <a> 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. */}
<div className="px-5 pt-3 pb-1 border-t border-white/10">
<span className="text-2xs text-sidebar-text/70 font-mono">
Built and maintained by{' '}
<a
<ExternalLink
href="https://www.linkedin.com/in/shankar-k-a1b6853ba"
target="_blank"
rel="me noopener noreferrer"
className="text-sidebar-text/90 hover:text-white transition-colors underline-offset-2 hover:underline"
title="Shankar on LinkedIn — opens in a new tab"
>
Shankar
</a>
</ExternalLink>
</span>
</div>
+3 -2
View File
@@ -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<string, string> = {
Active: '#10b981',