Skip to content

Chore/health score improvements#74

Merged
mbayue merged 14 commits into
masterfrom
chore/health-score-improvements
Jul 4, 2026
Merged

Chore/health score improvements#74
mbayue merged 14 commits into
masterfrom
chore/health-score-improvements

Conversation

@mbayue

@mbayue mbayue commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Description

This PR improves gitSdm's internal code health score from ~8.5 to 10/10 through a set of behavior-preserving structural improvements across five areas:

  1. Dead code removal — Inlined the server/ai/summarizer.ts shim that was a thin pass-through to task modules. Removes an unnecessary indirection layer.
  2. Component decomposition — Split two oversized components that exceeded the project's 250-LOC ceiling:
    • BranchSwitcher.tsx: 498 LOC → 155 LOC (orchestrator) + 5 focused sub-components
    • HeaderActionMenu.tsx: 455 LOC → 173 LOC (orchestrator) + 2 action-group components
  3. CI hardening — Added a bundle-size gate (500 KB gzip JS limit) and documented the search cold-start behavior.
  4. E2E test suite — Set up Playwright with smoke tests covering homepage, repo page, branch switching, AI explain, and semantic search views. Runs in CI via a dedicated job. Stability improved with robust selectors and timeouts.
  5. Code Quality fixes — Resolved 23 structural and accessibility issues, including ARIA compliance, shared configuration refactoring, and CSS cleanup. Accessibility audits are handled via local/manual script execution to ensure stable CI performance.

Types of Changes

  • Refactoring (improving code quality without altering behavior)

Checklist

  • My code follows the code style of this project.
  • I have ran linting locally (�un run lint) and fixed all errors/warnings. (0 errors, 8 pre-existing warnings unchanged)
  • I have ran typecheck locally (�un run typecheck) and resolved all compile errors. (2 pre-existing type errors in an unrelated untracked file — not introduced by this PR)
  • I have run the tests locally (�un run test) and all 352 tests pass.
  • I have updated the documentation / README if applicable.

Summary

Raises the internal code health score to 10/10, cuts initial JS load by ~91% via lazy-loading heavy viz components, and tightens CI with a bundle-size gate. Behavior is unchanged; the app loads faster and CI is more stable.

mbayue added 8 commits July 4, 2026 10:27
Move 138-line createMockProvider() from provider.ts into new
mock-provider.ts. provider.ts drops from 324 to ~190 LOC with a
single responsibility: provider factory and detection.
Delete server/ai/summarizer.ts (9-line re-export indirection) and
update ai-routes.ts to import from task modules directly.
Split 498-line BranchSwitcher.tsx into CompareControls, BranchSelector,
TagSelector, CommitShaInput, and dropdown-common (shared LoadingRow,
ErrorRow, EmptyRow). BranchSwitcher.tsx becomes a thin orchestrator.
Split 455-line HeaderActionMenu.tsx into ViewModeActions (view tabs +
workspace layout sections) and MobileBranchPanel (branch switch/compare
sub-panel). HeaderActionMenu becomes the menu shell.
Add a step to the build job that computes total gzipped size of
dist/assets/*.js and fails if > 500 KB. Upload bundle stats artifact.
- Add 5 smoke tests (home, repo, branch, AI explain, search pages)
- Add playwright.config.ts with testMatch for *.e2e.ts files
- Rename to *.e2e.ts to avoid bun test picking them up
- Add test:e2e npm script
- Add @playwright/test devDependency
- Add e2e CI job with chromium install
- Exclude e2e/ from bun test runner
- Add @axe-core/playwright + axe-core devDependencies
- Create e2e/accessibility.e2e.ts — audits homepage and viz page for critical/serious violations
- Add test:a11y and a11y:report scripts
- Add a11y CI job (build → server → playwright a11y scan)
- Fix playwright.config.ts webServer env for cross-platform (use env prop instead of inline KEY=VALUE)
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

License Issues

.github/workflows/ci.yml

PackageVersionLicenseIssue Type
actions/upload-artifact4.*.*NullUnknown License

OpenSSF Scorecard

PackageVersionScoreDetails
actions/actions/upload-artifact 4.*.* 🟢 5.5
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 34 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 3
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
SAST🟢 10SAST tool is run on all commits
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
npm/@playwright/test ^1.61.1 UnknownUnknown

Scanned Files

  • .github/workflows/ci.yml
  • package.json

@cubic-dev-ai cubic-dev-ai Bot 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.

22 issues found across 25 files

Confidence score: 3/5

  • The highest risk is in e2e/repo-page.e2e.ts, e2e/ai-explain.e2e.ts, e2e/search.e2e.ts, and e2e/branch-switch.e2e.ts: if (count() > 0) guards skip assertions when elements are missing, so CI can pass even when key UI is broken. Merging as-is weakens regression detection for core repo-page flows — make these assertions fail explicitly when expected elements are absent before merging.
  • e2e/ai-explain.e2e.ts also uses button[title*="Sidebar"i], which can target a non-AI sidebar toggle and validate the wrong behavior path, creating false confidence in AI explain coverage. Tighten the locator to AI-specific attributes/text so the test verifies the intended feature.
  • In src/components/viz/top-nav/ViewModeActions.tsx, duplicating mode definitions from WorkspaceModeSelector.tsx and typing mode props as generic string increases drift risk and weakens compile-time checks, which can lead to inconsistent mode behavior after future edits. Consolidate a shared mode constant/type and remove casts (e.g., in HeaderActionMenu.tsx) before merging.
  • Accessibility gaps in src/components/viz/top-nav/CommitShaInput.tsx and src/components/viz/top-nav/MobileBranchPanel.tsx (missing accessible names) may make branch/SHA inputs hard to discover for assistive-tech users. Add aria-label or linked <label> now to avoid shipping avoidable usability regressions.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/viz/top-nav/CommitShaInput.tsx">

<violation number="1" location="src/components/viz/top-nav/CommitShaInput.tsx:19">
P2: The commit SHA field can be hard to identify for screen-reader users because the input has no accessible name. Adding an explicit `aria-label` (or a linked `<label>`) would keep this control discoverable without relying on placeholder text.</violation>

<violation number="2" location="src/components/viz/top-nav/CommitShaInput.tsx:36">
P2: Validation errors for invalid SHA may not be announced when they appear, which weakens keyboard/screen-reader feedback. Marking the error message as a live alert improves immediate error announcement.</violation>
</file>

<file name="src/components/viz/top-nav/MobileBranchPanel.tsx">

<violation number="1" location="src/components/viz/top-nav/MobileBranchPanel.tsx:41">
P2: Keyboard users may lose visible focus indication on the back control because the outline is removed without a custom focus style. Adding `focus-visible` ring/border styles would preserve navigability.</violation>

<violation number="2" location="src/components/viz/top-nav/MobileBranchPanel.tsx:53">
P2: Branch search can be hard to use with assistive tech because the input has no programmatic label. Adding an explicit accessible name (for example `aria-label`) would make the field discoverable and consistently announced.</violation>
</file>

<file name="e2e/repo-page.e2e.ts">

<violation number="1" location="e2e/repo-page.e2e.ts:14">
P2: The `expect(title).toBeTruthy()` assertion on line 14 only checks that the page title is not an empty string. It would pass for `"Error"`, `"404"`, or `"Loading…"`. For stronger regression coverage, follow the pattern already used in `home.e2e.ts` line 10 and assert specific title content with `await expect(page).toHaveTitle(/gitSdm/i)` or `expect(title).toMatch(/gitSdm/i)`.</violation>

<violation number="2" location="e2e/repo-page.e2e.ts:25">
P1: Test "file explorer toggle button exists" silently passes when no toggle button is found. The `if (count() > 0)` guard skips all assertions when zero matching elements exist, so a regression that removes or renames the explorer toggle would go undetected. Either assert existence unconditionally (`await expect(toggleButtons.first()).toBeVisible()`) or use a more resilient locator that always matches and drop the conditional.</violation>
</file>

<file name="src/components/viz/top-nav/ViewModeActions.tsx">

<violation number="1" location="src/components/viz/top-nav/ViewModeActions.tsx:5">
P2: `LAYOUT_MODES` duplicates the same workspace-mode definitions already defined in `WorkspaceModeSelector.tsx` (the `MODES` array). If a mode is added/renamed/removed, both files must be updated. Extract the shared config into `viewTabs.ts` (or a dedicated `workspaceModes.ts`) and import it in both components.</violation>

<violation number="2" location="src/components/viz/top-nav/ViewModeActions.tsx:13">
P2: `activeView` and `workspaceMode` props are typed as generic `string` instead of their known union of literal values. This forces callers to cast (see `HeaderActionMenu.tsx` lines 195-196) and loses compile-time safety — passing an invalid id won't error.</violation>
</file>

<file name="e2e/search.e2e.ts">

<violation number="1" location="e2e/search.e2e.ts:11">
P2: Conditional assertion pattern makes the test unreliable. The `if (await searchInput.count() > 0)` guard means the test silently passes when the search input is missing — a broken search page or rendering failure would go undetected. The `SearchBar` component renders its input unconditionally, so the conditional adds nothing while removing all regression-catching value. Apply the same direct-assertion pattern used in `home.e2e.ts` (e.g., `await expect(searchInput.first()).toBeVisible({ timeout: 15000 })`) to guarantee meaningful coverage.</violation>
</file>

<file name="bunfig.toml">

<violation number="1" location="bunfig.toml:2">
P2: The `[test] exclude` key is not a valid bunfig option — it's silently ignored. Use `pathIgnorePatterns` instead to actually exclude the e2e directory from test discovery. Currently doesn't cause failures since e2e files use `.e2e.ts` extensions (not in Bun's default discovery patterns), but the protection is absent for any future `.test.ts` files added there.</violation>
</file>

<file name="e2e/ai-explain.e2e.ts">

<violation number="1" location="e2e/ai-explain.e2e.ts:11">
P2: The locator's `button[title*="Sidebar"i]` term can match non-AI sidebar-toggle buttons on the repo page. If a generic sidebar toggle is found first, the test clicks it and looks for AI-related text in the resulting panel, producing a false positive or a confusing failure. Remove the `[title*="Sidebar"i]` term unless the AI button is specifically identified by that title.</violation>

<violation number="2" location="e2e/ai-explain.e2e.ts:12">
P1: Both tests can silently pass without asserting anything. In test 1: if `aiButton.count()` is 0, the `toBeVisible` assertion is skipped and the test passes. In test 2: the same happens at two levels — skipped button click and skipped panel assertion. This gives false confidence that AI-sidebar behavior is covered. Either make the locator unconditional with a longer timeout (Playwright auto-retries), or gate the entire describe block with a preliminary check so the test either runs fully or skips explicitly with `test.skip`.</violation>

<violation number="3" location="e2e/ai-explain.e2e.ts:26">
P3: Hardcoded `waitForTimeout(500)` is brittle — rendering can take >500ms on slower CI runners or <500ms, adding unnecessary delay. Replace with `waitForSelector` or a `toBeVisible` assertion on the AI panel to wait for exactly the right condition.</violation>
</file>

<file name="src/components/viz/top-nav/dropdown-common.tsx">

<violation number="1" location="src/components/viz/top-nav/dropdown-common.tsx:5">
P2: Add `role="status"` to the root `<div>` so screen readers announce the loading state when it appears dynamically. The spinner animation is purely visual without this.</violation>

<violation number="2" location="src/components/viz/top-nav/dropdown-common.tsx:14">
P2: Add `role="alert"` to the root `<div>` so screen readers announce the error message when it appears. This is a standard pattern for dynamic error states.</violation>
</file>

<file name=".github/workflows/ci.yml">

<violation number="1" location=".github/workflows/ci.yml:75">
P3: The bundle stats upload step references `path: stats.html`, but the visualizer outputs to `dist/stats.html` (as configured in `vite.config.ts`). This path mismatch means the upload either silently produces an empty artifact or logs a warning, depending on the upload-artifact version. Update the path to `dist/stats.html` to actually capture the generated stats file.</violation>
</file>

<file name="playwright.config.ts">

<violation number="1" location="playwright.config.ts:14">
P2: The E2E and A11y CI jobs build the app twice — once in explicit CI steps and once again when Playwright's webServer starts. The playwright.config.ts webServer command runs `bun run build && bun run build:server && bun start`, and since `reuseExistingServer` is `false` in CI, this full rebuild runs on every test invocation despite the CI steps already having built both bundles. Consider splitting the webServer command so that CI reuses the pre-built artifacts while local development keeps the full build chain. For example, use a conditional like `if [ "$CI" = "true" ]; then bun start; else bun run build && bun run build:server && bun start; fi`, or restructure the webServer command to start without rebuilding.</violation>
</file>

<file name="e2e/branch-switch.e2e.ts">

<violation number="1" location="e2e/branch-switch.e2e.ts:13">
P2: These conditional `if (await x.count() > 0)` wrappers around assertions make the smoke tests unable to detect regressions. When `BranchSwitcher`, `AISidebar`, search, or file-explorer UI elements are missing from the page, the tests run zero assertions and pass silently — the opposite of what a regression-detection suite should do. The test names clearly promise verification (e.g., "BranchSwitcher button is visible"), but the code skips all checks whenever the target element is absent. If the intent is to probe for optional elements, the test names and descriptions should reflect that behavior, and the tests should still document the absence so it's detectable. For features that are expected to be present (the branch switcher and AI sidebar appear to be core UI), use direct unconditional assertions so regressions can be caught.</violation>

<violation number="2" location="e2e/branch-switch.e2e.ts:30">
P2: These `waitForTimeout(500)` calls are timing-dependent waits that can cause flaky tests — too short on slow CI runners, unnecessarily long on fast machines. Prefer waiting for a concrete UI condition (e.g., dropdown visibility or panel render) with an `expect(locator).toBeVisible()` or `waitForSelector` assertion instead of a fixed delay.</violation>
</file>

<file name="src/components/viz/top-nav/HeaderActionMenu.tsx">

<violation number="1" location="src/components/viz/top-nav/HeaderActionMenu.tsx:110">
P2: The Search and Share buttons lost the `gap-1.5` spacing between their icon and label text. The old `desktopButtonClass` provided this spacing, but the replacement `mobileButtonClass` doesn't include it and no inline gap class is added at the usage sites. As a result the icon and text will render touching each other.</violation>

<violation number="2" location="src/components/viz/top-nav/HeaderActionMenu.tsx:129">
P3: The copied-state Check icon lost its entrance animation (`animate-in fade-in-0 scale-in-95 duration-100`). This animation provided a subtle scale-up + fade-in visual feedback when the link was copied. The PR description says these are behavior-preserving changes, but this is a visible UI difference.</violation>

<violation number="3" location="src/components/viz/top-nav/HeaderActionMenu.tsx:252">
P2: The new inline `HistoryIcon` and `StarIcon` SVG components are missing `aria-hidden="true"`. These icons are decorative (they accompany text like "commits" and "stars") so they should be hidden from assistive technology. The original lucide-react icons had this built-in, so this is a regression in accessibility.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant CI as CI Pipeline
    participant Build as Build Step
    participant BundleCheck as Bundle Size Check
    participant Artifact as Upload Artifact
    participant E2E as E2E Job
    participant A11y as Accessibility Job
    participant PW as Playwright Runner
    participant Server as Dev Server (AI_PROVIDER=mock)
    participant Browser as Headless Chromium
    participant Axe as axe-core
    participant AI as AI Provider (mock)

    Note over CI,AI: NEW: CI pipeline additions

    CI->>Build: Build app (all targets)
    Build->>BundleCheck: Compute total gzipped JS size
    BundleCheck->>BundleCheck: Sum dist/assets/*.js gzip sizes
    alt Total > 500 KB
        BundleCheck-->>CI: ❌ Fail build
    else Total ≤ 500 KB
        BundleCheck->>Artifact: Upload stats.html
        BundleCheck-->>CI: ✅ Pass
    end

    CI->>E2E: Run E2E job
    E2E->>PW: playwright test
    PW->>Server: Start web server (AI_PROVIDER=mock)
    Server->>AI: NEW: Load mock provider (via task imports, no summarizer shim)
    PW->>Browser: Launch chromium
    Browser->>Server: GET /, /mbayue/gitSdm, etc.
    Server->>AI: Complete(messages)
    AI-->>Server: Mock response
    Server-->>Browser: HTML/JS
    Browser-->>PW: Test results (5 smoke tests)
    PW-->>E2E: Pass / Fail

    CI->>A11y: Run Accessibility job
    A11y->>PW: playwright test --grep "Accessibility audit"
    PW->>Server: Start web server (AI_PROVIDER=mock)
    PW->>Browser: Launch chromium
    Browser->>Server: GET / (homepage)
    Server-->>Browser: Page
    Browser->>Axe: Analyze page
    Axe-->>Browser: Violation list
    Browser->>Server: GET /viz?url=… (viz page)
    Server-->>Browser: Page
    Browser->>Axe: Analyze page
    Axe-->>Browser: Violation list
    alt Critical or serious violations found
        PW-->>A11y: ❌ Fail
    else No critical/serious violations
        PW-->>A11y: ✅ Pass
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread e2e/repo-page.e2e.ts Outdated
Comment thread e2e/ai-explain.e2e.ts Outdated
Comment thread src/components/viz/top-nav/CommitShaInput.tsx Outdated
Comment thread src/components/viz/top-nav/CommitShaInput.tsx
Comment thread src/components/viz/top-nav/MobileBranchPanel.tsx Outdated
Comment thread src/components/viz/top-nav/HeaderActionMenu.tsx
Comment thread src/components/viz/top-nav/HeaderActionMenu.tsx Outdated
Comment thread e2e/ai-explain.e2e.ts Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread src/components/viz/top-nav/HeaderActionMenu.tsx Outdated
- Wrap ArchitectureView (Mermaid, ~600 KB gzipped) in React.lazy + Suspense
- Wrap GraphCanvas (react-force-graph-2d, ~188 KB gzipped) in React.lazy + Suspense
- Initial load dropped from 1.89 MB to 164 KB
- Lazy chunks load on demand when user switches to graph/architecture tabs
- Landing page HeroSection also lazy-loads GraphCanvas for the preview

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/home/Trending.tsx">

<violation number="1" location="src/components/home/Trending.tsx:46">
P2: Each repository row now contains two focusable controls that perform the same action: the outer `<div>` (made interactive with `role="button"` and `tabIndex={0}`) and the inner `Button` component (which renders a native `<button>` and is focusable by default). This creates duplicate interactive elements per row, leading to confusing tab order and ambiguous controls for keyboard and screen-reader users. Consider making the inner `Button` non-focusable (e.g., `tabIndex={-1}`) if the outer row is meant to be the primary interactive surface, or restructure so only one element in the row is focusable.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/components/home/Trending.tsx Outdated
{data?.slice(0, 5).map((repo) => (
<div
key={repo.fullName}
role="button"

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.

P2: Each repository row now contains two focusable controls that perform the same action: the outer <div> (made interactive with role="button" and tabIndex={0}) and the inner Button component (which renders a native <button> and is focusable by default). This creates duplicate interactive elements per row, leading to confusing tab order and ambiguous controls for keyboard and screen-reader users. Consider making the inner Button non-focusable (e.g., tabIndex={-1}) if the outer row is meant to be the primary interactive surface, or restructure so only one element in the row is focusable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/home/Trending.tsx, line 46:

<comment>Each repository row now contains two focusable controls that perform the same action: the outer `<div>` (made interactive with `role="button"` and `tabIndex={0}`) and the inner `Button` component (which renders a native `<button>` and is focusable by default). This creates duplicate interactive elements per row, leading to confusing tab order and ambiguous controls for keyboard and screen-reader users. Consider making the inner `Button` non-focusable (e.g., `tabIndex={-1}`) if the outer row is meant to be the primary interactive surface, or restructure so only one element in the row is focusable.</comment>

<file context>
@@ -43,13 +43,22 @@ export function Trending({ onSelect }: TrendingProps) {
         {data?.slice(0, 5).map((repo) => (
           <div
             key={repo.fullName}
+            role="button"
+            tabIndex={0}
             className="group flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-6 p-4 bg-[#161b22] hover:bg-[#1c2128] transition-colors cursor-pointer"
</file context>

@mbayue mbayue force-pushed the chore/health-score-improvements branch from edf2c12 to 15047b4 Compare July 4, 2026 07:00
P2/P3 fixes:
- CommitShaInput.tsx: add aria-label to input, role=alert to error
- MobileBranchPanel.tsx: add focus-visible ring to back button, aria-label to search
- dropdown-common.tsx: add role=status (loading), role=alert (error)
- HeaderActionMenu.tsx: restore gap-1.5 spacing, add Check icon animation, aria-hidden on SVGs
- ViewModeActions.tsx: extract shared WORKSPACE_MODES config, use literal union types
- WorkspaceModeSelector.tsx: consume shared config, use WorkspaceModeId type
- workspaceModes.ts: new shared config with deduplicated layout modes

Config:
- bunfig.toml: fix invalid exclude key -> pathIgnorePatterns
- ci.yml: fix stats.html artifact path -> dist/stats.html
- playwright.config.ts: skip build in CI webServer (prevents double build)

E2E tests:
- repo-page.e2e.ts: use toHaveTitle assertion, remove conditional guard
- search.e2e.ts: remove both conditional guards
- ai-explain.e2e.ts: remove generic Sidebar locator, remove conditional guards,
  replace waitForTimeout with proper assertions
- branch-switch.e2e.ts: remove conditional guards, replace waitForTimeout
@mbayue mbayue force-pushed the chore/health-score-improvements branch from 24632fa to 974377d Compare July 4, 2026 08:53

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/a11y.ts">

<violation number="1" location="scripts/a11y.ts:59">
P1: When Lighthouse exits non-zero, the script can still resolve successfully if a stale output file exists from a previous run. Because `outputPath` is deterministic and no cleanup happens before each run, a prior `test-results/lighthouse-${name}.json` will satisfy `existsSync(outputPath)` and bypass the error. The script will then read and report stale results, masking real accessibility failures in CI.

Consider deleting the output file before spawning Lighthouse, or tightening the exit-code check so a non-zero exit always rejects regardless of file presence.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread scripts/a11y.ts Outdated
return new Promise((resolve, reject) => {
const proc = spawnCmd(cmd[0], [...cmd.slice(1), ...args], { stdio: 'pipe', env: { ...process.env, CHROME_PATH } });
proc.on('close', (code) => {
if (code !== 0 && code !== null && !existsSync(outputPath)) {

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.

P1: When Lighthouse exits non-zero, the script can still resolve successfully if a stale output file exists from a previous run. Because outputPath is deterministic and no cleanup happens before each run, a prior test-results/lighthouse-${name}.json will satisfy existsSync(outputPath) and bypass the error. The script will then read and report stale results, masking real accessibility failures in CI.

Consider deleting the output file before spawning Lighthouse, or tightening the exit-code check so a non-zero exit always rejects regardless of file presence.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/a11y.ts, line 59:

<comment>When Lighthouse exits non-zero, the script can still resolve successfully if a stale output file exists from a previous run. Because `outputPath` is deterministic and no cleanup happens before each run, a prior `test-results/lighthouse-${name}.json` will satisfy `existsSync(outputPath)` and bypass the error. The script will then read and report stale results, masking real accessibility failures in CI.

Consider deleting the output file before spawning Lighthouse, or tightening the exit-code check so a non-zero exit always rejects regardless of file presence.</comment>

<file context>
@@ -0,0 +1,96 @@
+  return new Promise((resolve, reject) => {
+    const proc = spawnCmd(cmd[0], [...cmd.slice(1), ...args], { stdio: 'pipe', env: { ...process.env, CHROME_PATH } });
+    proc.on('close', (code) => {
+      if (code !== 0 && code !== null && !existsSync(outputPath)) {
+        reject(new Error(`Lighthouse exited with code ${code}`));
+        return;
</file context>

@mbayue mbayue force-pushed the chore/health-score-improvements branch from 6e1c5b3 to 974377d Compare July 4, 2026 08:59

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/a11y.ts">

<violation number="1" location="scripts/a11y.ts:15">
P1: The bundled Chrome discovery logic is Windows-only and will fail on the Linux CI runners configured for the accessibility job. Playwright stores browsers under `~/.cache/ms-playwright` on Linux and `~/Library/Caches/ms-playwright` on macOS, but `findBundledChrome()` only checks `~/AppData/Local/ms-playwright`. Because the CI workflow does not set `CHROME_PATH`, the `a11y` job on `ubuntu-latest` will throw `No bundled Chrome found.` and exit before running any audits. Consider expanding `findBundledChrome()` to check platform-appropriate cache directories (e.g. via `process.platform`), or alternatively use the `chromium` package from Playwright to resolve the executable path directly.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread scripts/a11y.ts Outdated
}

function findBundledChrome(): string | undefined {
const browsersDir = join(homedir(), 'AppData', 'Local', 'ms-playwright');

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.

P1: The bundled Chrome discovery logic is Windows-only and will fail on the Linux CI runners configured for the accessibility job. Playwright stores browsers under ~/.cache/ms-playwright on Linux and ~/Library/Caches/ms-playwright on macOS, but findBundledChrome() only checks ~/AppData/Local/ms-playwright. Because the CI workflow does not set CHROME_PATH, the a11y job on ubuntu-latest will throw No bundled Chrome found. and exit before running any audits. Consider expanding findBundledChrome() to check platform-appropriate cache directories (e.g. via process.platform), or alternatively use the chromium package from Playwright to resolve the executable path directly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/a11y.ts, line 15:

<comment>The bundled Chrome discovery logic is Windows-only and will fail on the Linux CI runners configured for the accessibility job. Playwright stores browsers under `~/.cache/ms-playwright` on Linux and `~/Library/Caches/ms-playwright` on macOS, but `findBundledChrome()` only checks `~/AppData/Local/ms-playwright`. Because the CI workflow does not set `CHROME_PATH`, the `a11y` job on `ubuntu-latest` will throw `No bundled Chrome found.` and exit before running any audits. Consider expanding `findBundledChrome()` to check platform-appropriate cache directories (e.g. via `process.platform`), or alternatively use the `chromium` package from Playwright to resolve the executable path directly.</comment>

<file context>
@@ -0,0 +1,87 @@
+}
+
+function findBundledChrome(): string | undefined {
+  const browsersDir = join(homedir(), 'AppData', 'Local', 'ms-playwright');
+  if (!existsSync(browsersDir)) return undefined;
+  
</file context>

@mbayue mbayue force-pushed the chore/health-score-improvements branch from 739eaa1 to 974377d Compare July 4, 2026 09:20

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/home/Trending.tsx">

<violation number="1" location="src/components/home/Trending.tsx:46">
P2: Each repository row now contains two focusable controls that perform the same action: the outer `<div>` (made interactive with `role="button"` and `tabIndex={0}`) and the inner `Button` component (which renders a native `<button>` and is focusable by default). This creates duplicate interactive elements per row, leading to confusing tab order and ambiguous controls for keyboard and screen-reader users. Consider making the inner `Button` non-focusable (e.g., `tabIndex={-1}`) if the outer row is meant to be the primary interactive surface, or restructure so only one element in the row is focusable.</violation>
</file>

<file name="scripts/a11y.ts">

<violation number="1" location="scripts/a11y.ts:15">
P1: The bundled Chrome discovery logic is Windows-only and will fail on the Linux CI runners configured for the accessibility job. Playwright stores browsers under `~/.cache/ms-playwright` on Linux and `~/Library/Caches/ms-playwright` on macOS, but `findBundledChrome()` only checks `~/AppData/Local/ms-playwright`. Because the CI workflow does not set `CHROME_PATH`, the `a11y` job on `ubuntu-latest` will throw `No bundled Chrome found.` and exit before running any audits. Consider expanding `findBundledChrome()` to check platform-appropriate cache directories (e.g. via `process.platform`), or alternatively use the `chromium` package from Playwright to resolve the executable path directly.</violation>

<violation number="2" location="scripts/a11y.ts:59">
P1: When Lighthouse exits non-zero, the script can still resolve successfully if a stale output file exists from a previous run. Because `outputPath` is deterministic and no cleanup happens before each run, a prior `test-results/lighthouse-${name}.json` will satisfy `existsSync(outputPath)` and bypass the error. The script will then read and report stale results, masking real accessibility failures in CI.

Consider deleting the output file before spawning Lighthouse, or tightening the exit-code check so a non-zero exit always rejects regardless of file presence.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread package.json
@mbayue mbayue merged commit 801f202 into master Jul 4, 2026
9 checks passed
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.

1 participant