Skip to content

#3 made the base html, css and js template#9

Merged
Asifdotexe merged 6 commits into
mainfrom
3-frontend
Apr 5, 2026
Merged

#3 made the base html, css and js template#9
Asifdotexe merged 6 commits into
mainfrom
3-frontend

Conversation

@Asifdotexe

@Asifdotexe Asifdotexe commented Apr 4, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Interactive code-evolution visualizer with stacked-area chart, hover tooltips, repository selector, mode (chronological/identity) and scale (linear/log) toggles, responsive resize, and insights metrics.
    • Dark “glass” themed UI, loading/error states, animated chart rendering, and a complete static entry page.
  • Chores

    • Added manifest and multiple dataset snapshots to populate repositories.
    • Improved snapshot generation and persistence for updated dataset availability.

@Asifdotexe Asifdotexe self-assigned this Apr 4, 2026
@Asifdotexe Asifdotexe added the enhancement New feature or request label Apr 4, 2026
@Asifdotexe Asifdotexe linked an issue Apr 4, 2026 that may be closed by this pull request
3 tasks
@coderabbitai

coderabbitai Bot commented Apr 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a client-side visualization app and static UI to explore repository line-age: new HTML/CSS, a JavaScript TheseusVisualizer that loads data/manifest.json and per-repo snapshot JSON, renders interactive stacked-area SVG charts with tooltips/insights, new per-repo data files, and a refactored analysis script for incremental multi-repo processing.

Changes

Cohort / File(s) Summary
Visualizer Core
app.js
New TheseusVisualizer class: DOM boot, fetch data/manifest.json, repo selector, loadRepo(), data normalization, processData(), full SVG stacked-area rendering, gradients, axes, legend, tooltip/insights, loading/error UI, debounced resize.
Frontend Markup & Styles
index.html, style.css
New static page and stylesheet: hero, repo selector, svg#main-chart mount, loading/error states, tooltip element, controls for viz mode/scale, dark “glass” theme and responsive UI styles.
Repository Data & Manifest
data/manifest.json, data/langchain_data.json, data/react_data.json
Add manifest listing repo entries and multiple per-repo snapshot datasets (monthly/quarterly snapshots with per-year compositions).
Analysis Script
scripts/analyse_repository.py
Refactors snapshot selection (quarterly pre-2025, monthly 2025+), incremental processing per-year with atomic writes, configurable BLAME_WORKERS, structured logging, retry/chmod cleanup on failures, and concurrent processing of multiple TARGETS.
Repo Metadata
.gitignore
Minor update: add .agent to ignored entries and trim trailing whitespace.
Entry binding
index.html -> app.js
Wires TheseusVisualizer to DOMContentLoaded to initialize UI and auto-load the first repository.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Theseus as TheseusVisualizer
    participant Manifest as Manifest (data/manifest.json)
    participant RepoData as Repo Data (data/*.json)
    participant SVG as SVG Renderer
    participant Tooltip as Tooltip/Insights

    User->>Theseus: Page load (DOMContentLoaded)
    Theseus->>Manifest: fetch manifest.json
    Manifest-->>Theseus: repo list
    Theseus->>User: render repo selector
    User->>Theseus: select repo
    Theseus->>RepoData: fetch data/<repo_file>.json
    RepoData-->>Theseus: snapshot dataset
    Theseus->>Theseus: processData() (years, stacked geometry)
    Theseus->>SVG: renderChart() (areas, gradients, axes, legend)
    SVG-->>User: draw chart
    User->>SVG: mouse move
    SVG->>Theseus: pointer event -> find nearest snapshot
    Theseus->>Tooltip: showTooltip(snapshot)
    Tooltip-->>User: display totals + composition
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibbled manifests beneath the moon,

Stacked years like carrots, every bloom,
Gradients glow and tooltips hum,
Snapshots dance—the timeline’s drum,
Hooray, theseus sails and code finds room.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding foundational HTML, CSS, and JavaScript template files for the frontend visualization application.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 3-frontend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app.js`:
- Around line 217-225: The tooltip is being positioned with viewport coordinates
(e.clientX/e.clientY) but must use coordinates relative to the chart container;
in handleMouseMove compute container-relative coordinates using the svg bounding
rect (e.g., const rect = this.canvas.getBoundingClientRect()) and pass those to
showTooltip (for X: e.clientX - rect.left, or use the already-calculated mouseX
plus this.margin.left if showTooltip expects chart-content coordinates; for Y:
e.clientY - rect.top). Update the showTooltip calls in handleMouseMove (and the
other occurrences around the 229-233 region) to pass these container-relative
coordinates instead of raw clientX/clientY so the absolute-positioned tooltip
aligns with the chart.
- Around line 56-60: The code sets this.currentRepo before the fetch completes,
causing failed loads to leave loading state and block retries; change the flow
so you only assign this.currentRepo (and call updateActiveBtn) after the fetch
succeeds, and ensure showLoading(false) is called in both the fetch success and
catch/error paths; additionally, avoid the early-return "if (this.currentRepo
=== repoName) return" from preventing retries by either checking a separate
"loadingRepo" flag or clearing currentRepo on error so clicking the same repo
will retry (update references in the method containing currentRepo,
updateActiveBtn, showLoading).
- Around line 141-143: xScale and yScale can divide by zero for single-point or
all-zero datasets: guard (this.points.length - 1) and maxVal. Update xScale to
use a safe denominator (e.g., use Math.max(1, this.points.length - 1) or return
chartWidth/2 when this.points.length === 1) so x coordinate is valid, and update
yScale to handle maxVal === 0 by returning a baseline (e.g., chartHeight or
chartHeight/2) instead of dividing by zero; apply these changes where xScale and
yScale are defined to protect plotting for single-point or zero-valued datasets.
- Around line 269-274: The current calculation uses
Object.keys(first.composition)[0] and first.total_lines which yields an unstable
baseline and wrong denominator; instead determine the baseline year
deterministically by selecting the year key in first.composition with the
largest line count (e.g., iterate first.composition to find the year with max
value), set originalLinesInFirst = first.composition[originalYear], set
originalLinesInLast = last.composition[originalYear] || 0, guard against zero
before dividing, then compute replaced using those values and clamp as before
(update references to originalYear, originalLinesInFirst, originalLinesInLast,
replaced).

In `@data/manifest.json`:
- Around line 1-7: The manifest only lists "claude-code" but the data generation
in scripts/analyse_repository.py now produces additional datasets "react" and
"langchain"; update data/manifest.json to include entries for those datasets
(e.g., add objects for "react" with file "react_data.json" and "langchain" with
file "langchain_data.json") and provide concise descriptions so the UI selector
can surface them alongside "claude-code".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 796c5b3e-ac84-4264-a5d1-6cabbcbb0f27

📥 Commits

Reviewing files that changed from the base of the PR and between 9579aa7 and 9f659dd.

📒 Files selected for processing (5)
  • app.js
  • data/manifest.json
  • index.html
  • scripts/analyse_repository.py
  • style.css

Comment thread app.js Outdated
Comment thread app.js Outdated
Comment thread app.js Outdated
Comment thread app.js Outdated
Comment on lines +269 to +274
const originalYear = Object.keys(first.composition)[0];
const originalLinesInLast = last.composition[originalYear] || 0;
const originalLinesInFirst = first.total_lines;

const replaced = ((originalLinesInFirst - originalLinesInLast) / originalLinesInFirst) * 100;
document.getElementById('percent-replaced').textContent = `${Math.min(100, Math.max(0, replaced)).toFixed(1)}%`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

% replaced uses an unstable baseline and can report wrong values.

Object.keys(first.composition)[0] depends on key order, and first.total_lines is not the line count for that original year.

Suggested patch
-const originalYear = Object.keys(first.composition)[0];
-const originalLinesInLast = last.composition[originalYear] || 0;
-const originalLinesInFirst = first.total_lines;
+const compositionYears = Object.keys(first.composition).map(Number).sort((a, b) => a - b);
+if (compositionYears.length === 0) return;
+const originalYear = String(compositionYears[0]);
+const originalLinesInFirst = first.composition[originalYear] || 0;
+const originalLinesInLast = last.composition[originalYear] || 0;
+if (originalLinesInFirst === 0) return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const originalYear = Object.keys(first.composition)[0];
const originalLinesInLast = last.composition[originalYear] || 0;
const originalLinesInFirst = first.total_lines;
const replaced = ((originalLinesInFirst - originalLinesInLast) / originalLinesInFirst) * 100;
document.getElementById('percent-replaced').textContent = `${Math.min(100, Math.max(0, replaced)).toFixed(1)}%`;
const compositionYears = Object.keys(first.composition).map(Number).sort((a, b) => a - b);
if (compositionYears.length === 0) return;
const originalYear = String(compositionYears[0]);
const originalLinesInFirst = first.composition[originalYear] || 0;
const originalLinesInLast = last.composition[originalYear] || 0;
if (originalLinesInFirst === 0) return;
const replaced = ((originalLinesInFirst - originalLinesInLast) / originalLinesInFirst) * 100;
document.getElementById('percent-replaced').textContent = `${Math.min(100, Math.max(0, replaced)).toFixed(1)}%`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app.js` around lines 269 - 274, The current calculation uses
Object.keys(first.composition)[0] and first.total_lines which yields an unstable
baseline and wrong denominator; instead determine the baseline year
deterministically by selecting the year key in first.composition with the
largest line count (e.g., iterate first.composition to find the year with max
value), set originalLinesInFirst = first.composition[originalYear], set
originalLinesInLast = last.composition[originalYear] || 0, guard against zero
before dividing, then compute replaced using those values and clamp as before
(update references to originalYear, originalLinesInFirst, originalLinesInLast,
replaced).

Comment thread data/manifest.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
scripts/analyse_repository.py (1)

358-362: Nested concurrency can oversubscribe CPU/process resources.

Repo-level parallelism (Line 358) plus per-repo blame pools (Line 161) can multiply active threads/subprocesses and throttle throughput on large repos. Consider capping top-level workers via env var and/or deriving from CPU count.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/analyse_repository.py` around lines 358 - 362, Top-level
ThreadPoolExecutor currently uses max_workers=len(TARGETS) which can
oversubscribe when each repository spawns its own blame pools; change the
executor creation in the code that builds the executor for TARGETS to bound
max_workers by CPU or an environment variable (e.g., use min(len(TARGETS),
int(os.getenv("MAX_TOP_LEVEL_WORKERS", os.cpu_count() or 1)))) so
process_repository isn't started more concurrently than the machine can handle;
keep the rest of the submission logic the same so futures = {
executor.submit(process_repository, target, DATA_OUTPUT_DIR): target for target
in TARGETS } still maps targets to futures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/analyse_repository.py`:
- Around line 320-326: The cleanup handler handle_remove_readonly is too
permissive: it blindly sets mode 0o777 and swallows all exceptions; instead,
change the chmod to only add write permission bits (use stat to read current
mode and OR with stat.S_IWUSR|stat.S_IWGRP|stat.S_IWOTH) so you don't
over-broadly open files, and replace the broad "except Exception: pass" with a
targeted PermissionError handler that either logs the failure or re-raises it
(do not silently ignore other exceptions). Apply the same change to the
duplicate handler later in the file.
- Around line 72-79: The loop in get_snapshots currently overwrites
snapshots[period] for each commit, resulting in the oldest commit being kept; to
fix, only record the first (newest) commit seen for a period instead of
overwriting—e.g., in the loop that parses log_output into commit_hash and
commit_date and computes period, change the assignment to set snapshots[period]
only if period not in snapshots (or use snapshots.setdefault(period,
commit_hash)) so that the first/newest commit per period is preserved.
- Around line 158-161: The BLAME_WORKERS env parsing can raise ValueError and
crash the run; wrap the int(os.environ.get("BLAME_WORKERS", ...)) logic used to
set max_workers in a small validation block: try to read BLAME_WORKERS, attempt
int() inside try/except ValueError and fall back to the computed default
((os.cpu_count() or 1) * 2 capped at 20) if parsing fails or if parsed value <=
0; finally clamp the value to a minimum of 1 and optionally a sensible max, then
pass that safe max_workers into ThreadPoolExecutor. Reference symbols:
BLAME_WORKERS, max_workers, ThreadPoolExecutor, and the default expression
(min(20, (os.cpu_count() or 1) * 2)).

---

Nitpick comments:
In `@scripts/analyse_repository.py`:
- Around line 358-362: Top-level ThreadPoolExecutor currently uses
max_workers=len(TARGETS) which can oversubscribe when each repository spawns its
own blame pools; change the executor creation in the code that builds the
executor for TARGETS to bound max_workers by CPU or an environment variable
(e.g., use min(len(TARGETS), int(os.getenv("MAX_TOP_LEVEL_WORKERS",
os.cpu_count() or 1)))) so process_repository isn't started more concurrently
than the machine can handle; keep the rest of the submission logic the same so
futures = { executor.submit(process_repository, target, DATA_OUTPUT_DIR): target
for target in TARGETS } still maps targets to futures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd7943cb-7fea-4861-bde2-5e3a0f597f49

📥 Commits

Reviewing files that changed from the base of the PR and between 9f659dd and 4653455.

📒 Files selected for processing (3)
  • data/langchain_data.json
  • data/react_data.json
  • scripts/analyse_repository.py
✅ Files skipped from review due to trivial changes (2)
  • data/langchain_data.json
  • data/react_data.json

Comment thread scripts/analyse_repository.py
Comment thread scripts/analyse_repository.py Outdated
Comment thread scripts/analyse_repository.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
app.js (1)

91-117: ⚠️ Potential issue | 🟡 Minor

Previous retry-blocking issue addressed; add guard for missing repo.

The fix correctly sets currentRepo only after successful fetch (line 105) and uses finally to always clear loading state. However, repoInfo on line 98 could be undefined if repoName is not found in the manifest, causing a crash on line 99.

Proposed fix
     async loadRepo(repoName) {
         if (this.currentRepo === repoName && this.currentData) return;

         this.showLoading(true);
         this.hideError();

         try {
             const repoInfo = this.manifest.find(r => r.name === repoName);
+            if (!repoInfo) throw new Error(`Unknown repository: ${repoName}`);
             this.repoDescription.textContent = repoInfo.description || '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app.js` around lines 91 - 117, In loadRepo, guard against a missing manifest
entry: after retrieving repoInfo via this.manifest.find(...) check if repoInfo
is undefined and, if so, call this.showError(`Repository "${repoName}" not
found`) and return early so the code doesn't access repoInfo.description or
repoInfo.file; keep the rest of the try/finally unchanged so loading state is
still cleared in finally.
🧹 Nitpick comments (1)
app.js (1)

190-205: Previous single-point issue addressed; consider guarding empty datasets.

The xDenominator fix (line 191) correctly handles single-point datasets. The yScale fallback on line 204 handles maxVal === 0. However, if this.points is empty, Math.max(...[]) returns -Infinity, causing downstream issues. Consider adding an early return.

Proposed fix
         const chartWidth = width - this.margin.left - this.margin.right;
         const chartHeight = height - this.margin.top - this.margin.bottom;

+        if (this.points.length === 0) return;
+
         // Scales
         const xDenominator = Math.max(1, this.points.length - 1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app.js` around lines 190 - 205, The code doesn’t guard against an empty
dataset: Math.max(...this.points.map(...)) becomes -Infinity and breaks
xScale/yScale; update the top of the scale setup (around
xDenominator/xScale/maxVal) to check if this.points.length === 0 and return
early or render an empty chart state, otherwise compute xDenominator, xScale,
and maxVal as before; ensure yScale logic (yScaleMode, minLog/maxLog) only runs
when maxVal is finite and > 0 to avoid division by Infinity/NaN.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app.js`:
- Around line 424-432: Compute originalLinesInFirst from
first.composition[this.years[0]] (store in originalLinesInFirst) before the
guard and change the defensive check to ensure originalLinesInFirst is nonzero
(or handle zero by setting percent to '--' or 0) instead of only checking
first.total_lines; update the block that calculates replaced to use
originalLinesInFirst and guard against division by zero so
document.getElementById('percent-replaced').textContent never receives Infinity
(references: this.years, originalYear, first, last, originalLinesInFirst,
'percent-replaced').
- Around line 366-378: The tooltip percent calculation can produce NaN when
point.total === 0; update each percent expression that uses (X / point.total) in
the tooltip (references: originalVal, refactoredVal and any other value using
point.total) to guard against division by zero by conditionally computing the
percent (e.g., if point.total > 0 then compute ((X / point.total) *
100).toFixed(1) else use "0.0") and render that string followed by "%" so the UI
shows a stable "0.0%" instead of "NaN%".

In `@scripts/analyse_repository.py`:
- Around line 372-375: The MAX_TOP_LEVEL_WORKERS environment parsing needs the
same defensive handling used for BLAME_WORKERS: replace the direct
int(os.getenv("MAX_TOP_LEVEL_WORKERS", os.cpu_count() or 1)) call used to
compute max_top_level_workers with the same pattern that reads the env var as a
string, attempts to int() it in a try/except, falls back to os.cpu_count() (or
1) on ValueError or missing value, and logs or warns about invalid input; update
the assignment of max_top_level_workers (which currently uses TARGETS and
int(...)) to use this validated parsed value.

---

Duplicate comments:
In `@app.js`:
- Around line 91-117: In loadRepo, guard against a missing manifest entry: after
retrieving repoInfo via this.manifest.find(...) check if repoInfo is undefined
and, if so, call this.showError(`Repository "${repoName}" not found`) and return
early so the code doesn't access repoInfo.description or repoInfo.file; keep the
rest of the try/finally unchanged so loading state is still cleared in finally.

---

Nitpick comments:
In `@app.js`:
- Around line 190-205: The code doesn’t guard against an empty dataset:
Math.max(...this.points.map(...)) becomes -Infinity and breaks xScale/yScale;
update the top of the scale setup (around xDenominator/xScale/maxVal) to check
if this.points.length === 0 and return early or render an empty chart state,
otherwise compute xDenominator, xScale, and maxVal as before; ensure yScale
logic (yScaleMode, minLog/maxLog) only runs when maxVal is finite and > 0 to
avoid division by Infinity/NaN.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 430a844b-02ad-4db3-a606-b92bac3e8343

📥 Commits

Reviewing files that changed from the base of the PR and between 4653455 and 7874749.

📒 Files selected for processing (6)
  • .gitignore
  • app.js
  • data/manifest.json
  • index.html
  • scripts/analyse_repository.py
  • style.css
✅ Files skipped from review due to trivial changes (3)
  • data/manifest.json
  • index.html
  • style.css

Comment thread app.js
Comment on lines +366 to +378
<div class="value-group">
<strong>${originalVal.toLocaleString()}</strong>
<span class="percent-tag">${((originalVal / point.total) * 100).toFixed(1)}%</span>
</div>
</div>
<div class="tooltip-item">
<div class="label-group">
<span class="color-dot" style="background: #f0a33b"></span>
<span>Refactored</span>
</div>
<div class="value-group">
<strong>${refactoredVal.toLocaleString()}</strong>
<span class="percent-tag">${((refactoredVal / point.total) * 100).toFixed(1)}%</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tooltip percentages can show NaN% when point.total is zero.

Lines 368, 378, and 397 divide by point.total. If total is 0, this displays NaN%.

Proposed fix
-                        <span class="percent-tag">${((originalVal / point.total) * 100).toFixed(1)}%</span>
+                        <span class="percent-tag">${point.total > 0 ? ((originalVal / point.total) * 100).toFixed(1) : 0}%</span>

Apply similar guards to lines 378 and 397.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="value-group">
<strong>${originalVal.toLocaleString()}</strong>
<span class="percent-tag">${((originalVal / point.total) * 100).toFixed(1)}%</span>
</div>
</div>
<div class="tooltip-item">
<div class="label-group">
<span class="color-dot" style="background: #f0a33b"></span>
<span>Refactored</span>
</div>
<div class="value-group">
<strong>${refactoredVal.toLocaleString()}</strong>
<span class="percent-tag">${((refactoredVal / point.total) * 100).toFixed(1)}%</span>
<div class="value-group">
<strong>${originalVal.toLocaleString()}</strong>
<span class="percent-tag">${point.total > 0 ? ((originalVal / point.total) * 100).toFixed(1) : 0}%</span>
</div>
</div>
<div class="tooltip-item">
<div class="label-group">
<span class="color-dot" style="background: `#f0a33b`"></span>
<span>Refactored</span>
</div>
<div class="value-group">
<strong>${refactoredVal.toLocaleString()}</strong>
<span class="percent-tag">${point.total > 0 ? ((refactoredVal / point.total) * 100).toFixed(1) : 0}%</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app.js` around lines 366 - 378, The tooltip percent calculation can produce
NaN when point.total === 0; update each percent expression that uses (X /
point.total) in the tooltip (references: originalVal, refactoredVal and any
other value using point.total) to guard against division by zero by
conditionally computing the percent (e.g., if point.total > 0 then compute ((X /
point.total) * 100).toFixed(1) else use "0.0") and render that string followed
by "%" so the UI shows a stable "0.0%" instead of "NaN%".

Comment thread app.js
Comment on lines +424 to +432
let originalYear = this.years[0];
if (!originalYear || first.total_lines === 0) {
document.getElementById('percent-replaced').textContent = '--';
} else {
const originalLinesInFirst = first.composition[originalYear] || 0;
const originalLinesInLast = last.composition[originalYear] || 0;
const replaced = ((originalLinesInFirst - originalLinesInLast) / originalLinesInFirst) * 100;
document.getElementById('percent-replaced').textContent = `${Math.min(100, Math.max(0, replaced)).toFixed(1)}%`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Previous baseline stability issue addressed; guard mismatch remains.

Using this.years[0] (sorted) fixes the unstable baseline. However, the guard on line 425 checks first.total_lines === 0, but line 430 divides by originalLinesInFirst. If the original year has 0 lines but total_lines > 0, division by zero produces Infinity.

Proposed fix
         let originalYear = this.years[0];
-        if (!originalYear || first.total_lines === 0) {
+        const originalLinesInFirst = first.composition[originalYear] || 0;
+        if (!originalYear || originalLinesInFirst === 0) {
             document.getElementById('percent-replaced').textContent = '--';
         } else {
-            const originalLinesInFirst = first.composition[originalYear] || 0;
             const originalLinesInLast = last.composition[originalYear] || 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let originalYear = this.years[0];
if (!originalYear || first.total_lines === 0) {
document.getElementById('percent-replaced').textContent = '--';
} else {
const originalLinesInFirst = first.composition[originalYear] || 0;
const originalLinesInLast = last.composition[originalYear] || 0;
const replaced = ((originalLinesInFirst - originalLinesInLast) / originalLinesInFirst) * 100;
document.getElementById('percent-replaced').textContent = `${Math.min(100, Math.max(0, replaced)).toFixed(1)}%`;
}
let originalYear = this.years[0];
const originalLinesInFirst = first.composition[originalYear] || 0;
if (!originalYear || originalLinesInFirst === 0) {
document.getElementById('percent-replaced').textContent = '--';
} else {
const originalLinesInLast = last.composition[originalYear] || 0;
const replaced = ((originalLinesInFirst - originalLinesInLast) / originalLinesInFirst) * 100;
document.getElementById('percent-replaced').textContent = `${Math.min(100, Math.max(0, replaced)).toFixed(1)}%`;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app.js` around lines 424 - 432, Compute originalLinesInFirst from
first.composition[this.years[0]] (store in originalLinesInFirst) before the
guard and change the defensive check to ensure originalLinesInFirst is nonzero
(or handle zero by setting percent to '--' or 0) instead of only checking
first.total_lines; update the block that calculates replaced to use
originalLinesInFirst and guard against division by zero so
document.getElementById('percent-replaced').textContent never receives Infinity
(references: this.years, originalYear, first, last, originalLinesInFirst,
'percent-replaced').

Comment on lines +372 to 375
# Bound top-level workers by CPU count
max_top_level_workers = min(
len(TARGETS), int(os.getenv("MAX_TOP_LEVEL_WORKERS", os.cpu_count() or 1))
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

MAX_TOP_LEVEL_WORKERS parsing lacks the same validation applied to BLAME_WORKERS.

If MAX_TOP_LEVEL_WORKERS is set to a non-numeric value (e.g., "abc"), int() will raise ValueError and crash the script. Apply the same defensive parsing pattern used for BLAME_WORKERS (lines 161-172).

Proposed fix
-    # Bound top-level workers by CPU count
-    max_top_level_workers = min(
-        len(TARGETS), int(os.getenv("MAX_TOP_LEVEL_WORKERS", os.cpu_count() or 1))
-    )
+    # Bound top-level workers by CPU count
+    default_top_workers = os.cpu_count() or 1
+    raw_top_workers = os.getenv("MAX_TOP_LEVEL_WORKERS")
+    try:
+        parsed_top_workers = int(raw_top_workers) if raw_top_workers else default_top_workers
+    except ValueError:
+        logger.warning("Invalid MAX_TOP_LEVEL_WORKERS=%r, falling back to %d", raw_top_workers, default_top_workers)
+        parsed_top_workers = default_top_workers
+    max_top_level_workers = min(len(TARGETS), max(1, parsed_top_workers))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Bound top-level workers by CPU count
max_top_level_workers = min(
len(TARGETS), int(os.getenv("MAX_TOP_LEVEL_WORKERS", os.cpu_count() or 1))
)
# Bound top-level workers by CPU count
default_top_workers = os.cpu_count() or 1
raw_top_workers = os.getenv("MAX_TOP_LEVEL_WORKERS")
try:
parsed_top_workers = int(raw_top_workers) if raw_top_workers else default_top_workers
except ValueError:
logger.warning("Invalid MAX_TOP_LEVEL_WORKERS=%r, falling back to %d", raw_top_workers, default_top_workers)
parsed_top_workers = default_top_workers
max_top_level_workers = min(len(TARGETS), max(1, parsed_top_workers))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/analyse_repository.py` around lines 372 - 375, The
MAX_TOP_LEVEL_WORKERS environment parsing needs the same defensive handling used
for BLAME_WORKERS: replace the direct int(os.getenv("MAX_TOP_LEVEL_WORKERS",
os.cpu_count() or 1)) call used to compute max_top_level_workers with the same
pattern that reads the env var as a string, attempts to int() it in a
try/except, falls back to os.cpu_count() (or 1) on ValueError or missing value,
and logs or warns about invalid input; update the assignment of
max_top_level_workers (which currently uses TARGETS and int(...)) to use this
validated parsed value.

@Asifdotexe Asifdotexe merged commit 9772c4e into main Apr 5, 2026
2 checks passed
@Asifdotexe Asifdotexe deleted the 3-frontend branch April 8, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Frontend

1 participant