Skip to content

Feat: HTML Report Design Updates - #10

Merged
JosephMaynard merged 32 commits into
masterfrom
feat/report-design-updates
Feb 17, 2026
Merged

JosephMaynard merged 32 commits into
masterfrom
feat/report-design-updates

Conversation

@JosephMaynard

@JosephMaynard JosephMaynard commented Feb 5, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Favicon added and header logo redesigned.
    • Richer package insights: fileCount and hasBin included; upgrade blocker detection now considers install scripts.
    • Improved report linking: name-based dependency resolution and more reliable link/scroll reveal.
  • Bug Fixes

    • CLI: Yarn PnP detection and clearer handling when Yarn outdated is unavailable.
  • Documentation

    • README expanded with detailed scan workflow, usage heuristics, package manager notes, and examples.
  • Chores

    • Added fixture install/scan scripts and many new test fixtures (usage, execution, license).

@coderabbitai

coderabbitai Bot commented Feb 5, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.
📝 Walkthrough

Walkthrough

Replaced the inline header SVG and added an inline-SVG favicon; extended dependency metadata with fileCount and hasBin and added 'installScripts' as an upgrade blocker; added Yarn PnP detection and explicit Yarn-outdated handling; implemented name→dep-key resolution and forced-visibility in the report UI; added many test fixtures and README/script updates.

Changes

Cohort / File(s) Summary
UI: Logo & favicon
report-ui/index.html, src/report.ts
Inserted inline-SVG data-url favicon and replaced/restructured header SVG (root class change, new gradients, shapes, styles).
Report frontend: dependency linking & rendering
report-ui/main.ts
Added dep-key parsing/resolution, keys-by-name cache, resolveDepLinkTarget, forced-visible dep logic; updated renderers to prefer resolved keys, two‑phase scroll on activation, and filter control handling.
Types: package metadata
src/types.ts, report-ui/types.ts
Added fileCount?: number and hasBin?: true to DependencyRecord.package; extended upgrade.blockers union with 'installScripts'.
Aggregation & usage classification
src/aggregator.ts
Added file-category classification, runtimeImpact from categorized file lists, packageInsights/fileCount/hasBin propagation, requiredPeerDependencies counting, and richer upgrade-blocker detection.
CLI & workspace detection
src/cli.ts
Added centralized detectYarnPnP() and integrated into workspace detection and package-manager logic; treat Yarn PnP specially and merge peerDependencies during workspace merging.
npm outdated runner
src/runners/npmOutdated.ts
Added isYarnOutdatedUnsupported detector and branch to persist raw output for unsupported Yarn outdated; reordered parsing flow and improved Yarn-specific handling.
Report assets / minor
src/report-assets.ts, src/report.ts
Adjusted internal JS_CONTENT generator signature to accept an extra param; propagated calls; injected favicon into generated HTML.
Docs & scripts
README.md, package.json
Expanded README with execution pipeline and usage heuristics; documented fileCount/hasBin; added fixture install/scan scripts and included new fixture groups in aggregates.
Test fixtures: usage, execution, license suites
test-fixtures/usage-classification/..., test-fixtures/execution-signals/..., test-fixtures/license-edge-cases/...
Added many fixture packages, manifests, scripts, LICENSE files, native stub, and helper scripts to exercise usage classification, execution signals, and license edge cases.
Fixture helper scripts
test-fixtures/execution-signals/.../install-with-retries.js, .../install.js, postinstall.js, prepare.js
Added retrying install wrapper and small install/postinstall/prepare helper scripts used by new fixtures.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(200,200,255,0.5)
  participant CLI
  end
  rect rgba(200,255,200,0.5)
  participant Collectors as "Per-package Collectors"
  end
  rect rgba(255,200,200,0.5)
  participant Aggregator
  end
  rect rgba(255,255,200,0.5)
  participant ReportUI as "Report UI"
  end

  CLI->>Collectors: detect workspaces (detectYarnPnP) & run collectors (audit/outdated/graph)
  Collectors-->>Aggregator: persist per-package raw outputs
  Aggregator->>Aggregator: merge deps (incl. peers), compute packageStats (fileCount/hasBin), determineIntroduction(scope)
  Aggregator-->>ReportUI: emit aggregated model
  ReportUI->>ReportUI: resolve dep keys (keysByName), apply filters/forced visibility, render links and scroll to details
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Poem

🐰 I swapped a tiny logo, set a favicon aglow,
I counted files and bins where little packages grow,
I sniffed PnP paths, wrote Yarn's unsupported song,
I mapped keys by name and made the UI scroll along,
Fixtures sprout, a rabbit cheers — hop, tests run strong.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat: HTML Report Design Updates' is vague and overly broad. While it mentions 'design updates' for HTML reports, the changeset encompasses substantial infrastructure changes well beyond UI/design, including new type definitions, public API signatures, execution signal detection, license edge-case handling, usage classification fixtures, dependency resolution logic, and package metadata enhancements. Revise the title to reflect the primary change more specifically. Consider: 'Feat: Extend dependency metadata and add execution/license/usage test fixtures' or similar to accurately represent the scope of implementation beyond just visual design updates.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/report-design-updates

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@report-ui/main.ts`:
- Around line 263-268: The anchor generation uses getDepDomId(pkg) but
resolveDepKey returns null for name-only dep keys (strings without “@”), making
the link a no-op; update the code paths that build package links (the snippet
using escapeHtml(getDepDomId(pkg)) and the other occurrences around the
getDepDomId/resolveDepKey usage) to detect when resolveDepKey(pkg) is null and,
in that case, try resolving by name: call resolveDepKeyByName(pkgName) or filter
the dependency index for a single candidate and use that resolved key to compute
getDepDomId(resolvedKey); if multiple candidates exist keep the non-link
behavior, and ensure aria-label and data-dep-key are updated to use the resolved
key when present (apply same fix for the other occurrences noted).

In `@src/aggregator.ts`:
- Around line 865-882: The current regexes in isTestFile, isToolingFile, and
isBuildFile miss dotfile-style configs (e.g. .eslintrc, .prettierrc, .babelrc)
so those files are misclassified; update each function's regex to include an
alternative branch that matches leading-dot config filenames (e.g.
/(^|\/)\.(eslint|prettier|babel|stylelint|commitlint|husky|renovate|swc|tsconfig|postcss|tailwind|storybook|babel|eslintrc|prettierrc|babelrc)(rc|config)?(\..*)?$/
or equivalent) so files like .eslintrc, .prettierrc.json, .babelrc.js, and
.tsconfig (dot-prefixed or extensionless) are correctly detected by
isToolingFile and isBuildFile (and add any test-tool dotfiles to isTestFile as
needed).

In `@test-fixtures/execution-signals/packages/scripted/package.json`:
- Around line 6-8: The "install" script under scripts.install currently uses the
ambiguous `&& ||` pattern with scripts/install.js; replace that with an explicit
retry behavior: either change the package.json entry for scripts.install to run
scripts/install.js once and then retry on failure using a clear OR-based
sequence (script A || script A) or, preferably, create a small wrapper (e.g.,
scripts/install-with-retries.js) that attempts scripts/install.js N times and
call that from scripts.install; update references to scripts/install.js in
package.json accordingly so intent is unambiguous.

In `@test-fixtures/execution-signals/packages/scripted/scripts/install.js`:
- Around line 8-10: Replace the hard-coded dead branch and global eval by gating
these calls behind an environment flag and explicitly suppressing the
global-eval lint rule; specifically, change the constant-conditional block that
contains cp.execSync and eval so it runs only when a guard like
process.env.RUN_EXECUTION_SIGNALS === 'true' is set, and add an inline lint
suppression for the eval call (e.g., a comment to disable
lint/security/noGlobalEval) or remove the eval entirely if not required; update
the block referencing the if (false) conditional, cp.execSync, and eval to
implement this guard and suppression.
🧹 Nitpick comments (4)
src/types.ts (1)

97-100: Document presence-only semantics or switch to boolean for clarity.
Line 100 types hasBin as true (literal-true only), which communicates a presence-only flag: hasBin present means the package has a bin, and hasBin absent means we're not asserting it. This design is intentional (see line 233 in aggregator.ts where only true is set), but the unconventional literal-true type may confuse maintainers. Either add a clarifying comment that hasBin presence indicates true and absence means unknown, or switch to boolean for conventional clarity.

package.json (1)

27-43: Align fixture scan output extensions for consistency.

The new scans write .json while existing fixture scans still output .html with --json, so fixtures:scan now mixes extensions. Consider standardizing on one convention to keep tooling predictable.

README.md (1)

172-178: Reduce repeated “Yarn” phrasing in support list.

Three consecutive bullets start with “Yarn”; a grouped bullet reads cleaner.

✍️ Possible rewrite
-- Yarn Classic (v1, node_modules linker): Supported for dependency tree, audit, outdated, and workspaces.
-- Yarn Berry (v2+, node-modules linker): Dependency tree and audit work; outdated support depends on available Yarn commands/plugins and may be unavailable.
-- Yarn Plug'n'Play (`nodeLinker: pnp`): Not supported yet.
+- Yarn:
+  - Classic (v1, node_modules linker): Supported for dependency tree, audit, outdated, and workspaces.
+  - Berry (v2+, node-modules linker): Dependency tree and audit work; outdated support depends on available Yarn commands/plugins and may be unavailable.
+  - Plug'n'Play (`nodeLinker: pnp`): Not supported yet.
test-fixtures/usage-classification/src/tests/app.test.ts (1)

2-3: Duplicate import of transitive-mixed.

The module transitive-mixed is imported twice. If this is intentional (e.g., to test deduplication or import counting behavior), consider adding a comment explaining the purpose. Otherwise, remove the duplicate.

🔧 If unintentional, remove the duplicate
 import 'transitive-testing';
 import 'transitive-mixed';
-import 'transitive-mixed';

Comment thread report-ui/main.ts Outdated
Comment thread src/aggregator.ts
Comment thread test-fixtures/execution-signals/packages/scripted/package.json
Comment thread test-fixtures/execution-signals/packages/scripted/scripts/install.js Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/aggregator.ts`:
- Around line 1039-1050: determineIntroduction currently classifies any
peer-scoped dependency as 'tooling'; change the logic so only dev scope is
always 'tooling' and peer scope is treated as tooling only when its
runtimeImpact is not 'runtime' (e.g., runtimeImpact === 'tooling' or 'testing'
or undefined), e.g. replace the condition "if (scope === 'dev' || scope ===
'peer') return 'tooling';" with separate checks: "if (scope === 'dev') return
'tooling';" and then "if (scope === 'peer' && runtimeImpact !== 'runtime')
return 'tooling';" so peer deps with runtimeImpact === 'runtime' are not
misclassified.
🧹 Nitpick comments (2)
report-ui/main.ts (1)

266-285: Consider caching name-to-key mappings for better performance.

resolveDepKeyByNameFromSet iterates and parses the entire linkableKeys set on each call. For reports with many dependencies, this could become noticeable when rendering large dependency lists.

You already maintain depKeysByName in the init scope. Consider passing it (or a lookup function) to these resolution helpers, or memoizing results.

♻️ Example approach using a pre-built map
-function resolveDepKeyByNameFromSet(name: string, linkableKeys: Set<string>): string | null {
-  const candidates = Array.from(linkableKeys).filter((candidate) => {
-    const parsed = parseDepKey(candidate);
-    return parsed?.name === name;
-  });
-  if (candidates.length === 1) return candidates[0];
-  return null;
-}
+function resolveDepKeyByNameFromSet(
+  name: string,
+  linkableKeys: Set<string>,
+  keysByName?: Map<string, string[]>
+): string | null {
+  if (keysByName) {
+    const candidates = keysByName.get(name)?.filter(k => linkableKeys.has(k)) || [];
+    return candidates.length === 1 ? candidates[0] : null;
+  }
+  // Fallback to iteration if no map provided
+  const candidates = Array.from(linkableKeys).filter((candidate) => {
+    const parsed = parseDepKey(candidate);
+    return parsed?.name === name;
+  });
+  return candidates.length === 1 ? candidates[0] : null;
+}
src/aggregator.ts (1)

754-775: Optional: avoid re-classifying file categories during runtime impact weighting.
Entries already carry category, so you can reuse it instead of re-running regex classification.

♻️ Suggested tweak
-function determineRuntimeImpactFromFiles(
-  files: Array<{ file: string; count: number }>
-): DependencyRecord['usage']['runtimeImpact'] {
+function determineRuntimeImpactFromFiles(
+  files: Array<{ file: string; count: number; category?: RuntimeCategory }>
+): DependencyRecord['usage']['runtimeImpact'] {
   const weights: Record<RuntimeCategory, number> = {
     runtime: 0,
     build: 0,
     testing: 0,
     tooling: 0
   };
   let total = 0;

   for (const entry of files) {
-    const category = classifyFileCategory(entry.file);
+    const category = entry.category ?? classifyFileCategory(entry.file);
     const weight = Number.isFinite(entry.count) && entry.count > 0 ? entry.count : 1;
     weights[category] += weight;
     total += weight;
   }

Also applies to: 889-935

Comment thread src/aggregator.ts

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@report-ui/main.ts`:
- Around line 257-309: parseDepKey incorrectly splits scoped npm-alias keys;
update parseDepKey to first look for the last occurrence of the substring
"@npm:" (use lastIndexOf('@npm:')) and, if found, split at that '@' so
parsed.version begins with "npm:..."; otherwise fall back to the existing last-@
split. Also adjust resolveDepLinkTarget's npm alias construction (the branch
using parsed.version.startsWith('npm:')) to avoid producing a double '@' when
the aliased target starts with '@' — concatenate parsed.name and the aliased
target trimmed of its 'npm:' prefix, inserting an '@' only when that trimmed
target does not already start with '@'. Ensure changes touch parseDepKey and
resolveDepLinkTarget accordingly.

Comment thread report-ui/main.ts

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@report-ui/main.ts`:
- Around line 704-709: The UI renders two consecutive items with the same label
"Direct parents" — one via renderKvItemHtml('Direct parents',
renderDependencyIdList(..., dep.usage.origins.topParentPackages, ...)) and the
other via renderKvItem('Direct parents', dep.usage.origins.parentPackageCount ??
0); change the second item's label to something distinct (e.g., 'Parent count'
or 'Direct parents (count)') so the numeric parentPackageCount is not shown with
the same label; update the call to renderKvItem that references
dep.usage.origins.parentPackageCount accordingly.
🧹 Nitpick comments (1)
README.md (1)

209-211: Consider varying sentence structure for readability.

Three successive bullet points begin with "Yarn", which can feel repetitive. A minor rewording could improve flow.

📝 Suggested adjustment
 - npm: Supported for dependency tree, audit, outdated, single-package, and workspaces.
 - pnpm: Supported for dependency tree, audit, outdated, and workspaces (with ls depth fallbacks for large projects).
-- Yarn Classic (v1, node_modules linker): Supported for dependency tree, audit, outdated, and workspaces.
-- Yarn Berry (v2+, node-modules linker): Dependency tree and audit work; outdated support depends on available Yarn commands/plugins and may be unavailable.
-- Yarn Plug'n'Play (`nodeLinker: pnp`): Not supported yet.
+- Yarn Classic (v1, node_modules linker): Supported for dependency tree, audit, outdated, and workspaces.
+- Yarn Berry (v2+, node-modules linker): Dependency tree and audit work; outdated support depends on available commands/plugins and may be unavailable.
+- Plug'n'Play mode (`nodeLinker: pnp`): Not yet supported.

Comment thread report-ui/main.ts Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@report-ui/main.ts`:
- Around line 1128-1136: resolveDepKey builds the npm-alias key incorrectly for
scoped packages by inserting an extra '@' (producing '@scope/name@@1.2.3');
update the npmAliasKey construction in resolveDepKey to match the logic in
resolveDepLinkTarget (concatenate parsed.name with
parsed.version.slice('npm:'.length) without adding an extra '@' or otherwise use
the same expression used in resolveDepLinkTarget) so scoped aliases resolve
correctly; keep the check using depByKey.has(npmAliasKey) as-is.
🧹 Nitpick comments (2)
report-ui/main.ts (1)

971-979: Handle npm alias keys when building depKeysByName.

Using lastIndexOf('@') will mis-parse keys containing @npm: (especially scoped aliases), which can break name-based resolution later. Consider reusing parseDepKey/getDepKeysByNameIndex to keep alias handling consistent.

♻️ Suggested fix
-  const depKeysByName = new Map<string, string[]>();
-  depByKey.forEach((_dep, depKey) => {
-    const lastAt = depKey.lastIndexOf('@');
-    if (lastAt <= 0) return;
-    const name = depKey.slice(0, lastAt);
-    const keys = depKeysByName.get(name) || [];
-    keys.push(depKey);
-    depKeysByName.set(name, keys);
-  });
+  const depKeysByName = getDepKeysByNameIndex(knownDepKeys);
src/report-assets.ts (1)

16-16: Rename exported asset constants to camelCase (or document an exception).

JS_CONTENT (and CSS_CONTENT above) are exported variables; guidelines call for camelCase naming. Since this file is generated, consider updating the generator (scripts/build-report.ts) and any import sites, or explicitly documenting an exception for generated asset exports.

As per coding guidelines, use camelCase for variables and functions.

Comment thread report-ui/main.ts

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@report-ui/main.ts`:
- Around line 970-979: The current loop building depKeysByName uses manual
lastIndexOf('@') parsing which misparses keys with `@npm`: and scoped aliases;
replace this with the parse-aware indexer: call
getDepKeysByNameIndex(knownDepKeys) to produce the mapping (or iterate depByKey
and use parseDepKey(depKey) to extract the canonical name) and populate
depKeysByName accordingly so keys like `@npm`:... and scoped packages are indexed
correctly; update usages of knownDepKeys/depKeysByName to use the new map.

Comment thread report-ui/main.ts Outdated
Comment on lines +970 to +979
const knownDepKeys = new Set(depByKey.keys());
const depKeysByName = new Map<string, string[]>();
depByKey.forEach((_dep, depKey) => {
const lastAt = depKey.lastIndexOf('@');
if (lastAt <= 0) return;
const name = depKey.slice(0, lastAt);
const keys = depKeysByName.get(name) || [];
keys.push(depKey);
depKeysByName.set(name, keys);
});

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.

⚠️ Potential issue | 🟡 Minor

Use parseDepKey-aware indexing for depKeysByName. The manual lastIndexOf('@') parsing will mis-index dep keys containing @npm: (especially scoped aliases), which can break name-only resolution for those deps. Prefer getDepKeysByNameIndex(knownDepKeys) or parseDepKey here.

🛠️ Suggested fix
-  const depKeysByName = new Map<string, string[]>();
-  depByKey.forEach((_dep, depKey) => {
-    const lastAt = depKey.lastIndexOf('@');
-    if (lastAt <= 0) return;
-    const name = depKey.slice(0, lastAt);
-    const keys = depKeysByName.get(name) || [];
-    keys.push(depKey);
-    depKeysByName.set(name, keys);
-  });
+  const depKeysByName = getDepKeysByNameIndex(knownDepKeys);
🤖 Prompt for AI Agents
In `@report-ui/main.ts` around lines 970 - 979, The current loop building
depKeysByName uses manual lastIndexOf('@') parsing which misparses keys with
`@npm`: and scoped aliases; replace this with the parse-aware indexer: call
getDepKeysByNameIndex(knownDepKeys) to produce the mapping (or iterate depByKey
and use parseDepKey(depKey) to extract the canonical name) and populate
depKeysByName accordingly so keys like `@npm`:... and scoped packages are indexed
correctly; update usages of knownDepKeys/depKeysByName to use the new map.

@JosephMaynard
JosephMaynard merged commit ba6d3d2 into master Feb 17, 2026
1 check passed
@JosephMaynard
JosephMaynard deleted the feat/report-design-updates branch February 17, 2026 23:27
@coderabbitai coderabbitai Bot mentioned this pull request Mar 2, 2026
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