Skip to content

Migrate frontend to Svelte 5 + Vite + TypeScript#6

Merged
tyom merged 18 commits into
mainfrom
migrate-frontend-svelte-vite
May 22, 2026
Merged

Migrate frontend to Svelte 5 + Vite + TypeScript#6
tyom merged 18 commits into
mainfrom
migrate-frontend-svelte-vite

Conversation

@tyom
Copy link
Copy Markdown
Owner

@tyom tyom commented May 22, 2026

What & why

Replaces the 1,740-line vanilla-JS template.html monolith with a componentized, type-safe frontend built with Bun + Vite + Svelte 5 + TypeScript. The monolith had become hard to navigate; the dashboard logic is now ~13 focused, typed modules.

The end-user contract is unchanged. vite-plugin-singlefile emits one self-contained web/dist/index.html (all JS+CSS inlined) that still carries the /*__DATA_INJECTION__*/ marker; build.py embeds it into repo-intel.py exactly as before. Bun/Vite are build-time only — the shipped tool is still a zero-dependency Python executable installed via curl/Homebrew/Action.

Highlights

  • web/src/lib/ — dashboard engine split into one module per concern: heatmap, timeline (the big canvas one), charts, table, popovers, header, interactions, plus format/theme/avatar helpers, orchestrated by dashboard.ts. App.svelte holds the static structure; types.ts mirrors build_data().
  • Chart.js is now bundled (was a CDN fetch) and registered explicitly (only the controllers/scales/plugins in use), so the output is fully offline/self-contained. Bundle: ~280 kB / 95 kB gz; dist/repo-intel is 418 kB.
  • Pipeline rewired: build.py/repo-intel.py read web/dist/index.html; Makefile gains web-build/web-dev; the pre-commit hook rebuilds the bundle and watches web/ (needs Bun); ci.yml/release.yml add oven-sh/setup-bun@v2 pinned to 1.3.10.
  • Deletes template.html; updates README + .gitignore.

Verification

Rendered end-to-end through the real Python pipeline with zero console errors and full visual parity at:

  • 1 contributor (this repo)
  • 3 contributors (mock fixture)
  • toss/es-toolkit — 1,552 commits, 234 contributors, ~2-year span, release tags

Watch on first CI run

The staleness check (git diff --exit-code -- dist/repo-intel) assumes the Vite/esbuild bundle rebuilds byte-for-byte on Ubuntu CI vs the committer's machine. Same-machine rebuilds are bit-identical and Bun is pinned, but cross-OS reproducibility is unproven until this PR's CI runs. If it fails, weaken that step to "build succeeds + smoke test passes."

DX note

Contributors now need Bun installed to commit any web/-touching change (the pre-commit hook runs bun install && bun run build).

Summary by CodeRabbit

  • New Features

    • Interactive web dashboard added: timeline, heatmap, contributor profiles, charts, tech grid, tooltips/popovers and a bundled frontend with mock-data support.
  • Documentation

    • Development guide updated for the new frontend workflow, dev server, build steps and first-time setup notes.
  • Chores

    • Build/dev tasks, pre-commit hook and CI/release workflows updated to build and validate the frontend bundle; tooling, ignore rules and formatting commands added.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c97e17f5-1eb2-4f82-a4df-025d1e67998e

📥 Commits

Reviewing files that changed from the base of the PR and between 3dc1c6b and 392cf07.

⛔ Files ignored due to path filters (1)
  • dist/repo-intel is excluded by !**/dist/**
📒 Files selected for processing (4)
  • web/src/lib/actions.ts
  • web/src/lib/components/Heatmap.svelte
  • web/src/lib/timeline.ts
  • web/vite.config.ts
📜 Recent review details
🔇 Additional comments (10)
web/vite.config.ts (3)

31-37: Previous review suggested using fileURLToPath from node:url.

The manual implementation handles common cases (percent-decoding and Windows drive letters), but fileURLToPath is a Node.js built-in that handles additional edge cases like UNC paths. Since this was an intentional design decision to avoid @types/node in svelte-check's scope, marking as acknowledged.


5-24: LGTM!


39-48: LGTM!

web/src/lib/components/Heatmap.svelte (1)

42-42: LGTM!

Also applies to: 112-115, 202-211

web/src/lib/actions.ts (4)

95-105: LGTM!


14-70: LGTM!


119-152: LGTM!


154-231: LGTM!

web/src/lib/timeline.ts (2)

922-927: LGTM!


1179-1186: LGTM!


📝 Walkthrough

Walkthrough

This PR adds a Svelte/Vite frontend dashboard and integrates Bun-based frontend compilation into the existing Python single-file build. It updates hooks, Makefile, and CI/release workflows to run the frontend build, replaces template.html with web/dist/index.html as the injected template, and introduces a full set of UI components, types, styles, utilities, and mock data.

Changes

Frontend dashboard and build system integration

Layer / File(s) Summary
Build system and artifact integration
.githooks/pre-commit, .github/workflows/ci.yml, .github/workflows/release.yml, Makefile, build.py, repo-intel.py, README.md, .gitignore, .prettierignore
Pre-commit hook, CI/release workflows, and build scripts orchestrate Bun frontend build before creating the final Python artifact. Build sources now include web/ and build.py instead of template.html; build.py reads web/dist/index.html.
Frontend project configuration
web/package.json, web/tsconfig.json, web/vite.config.ts, .prettierrc, package.json
Svelte/Vite workspace configuration with npm scripts, TypeScript settings, Vite injection plugin and path aliases, and Prettier setup including Svelte plugin.
HTML entry point and bootstrap
web/index.html, web/src/main.ts, web/src/data.ts
HTML scaffold with data injection marker and Vite entrypoint. Main.ts bootstraps the app by loading injected or fetched data and mounting App. loadData returns window.DATA when present or fetches mock JSON.
Data types and mock data
web/src/types.ts, web/public/mock-data.json
TypeScript schema for injected RepoData and a complete mock dataset for local/frontend development.
Styling and theme configuration
web/src/styles/app.css, web/src/lib/theme.ts, web/src/lib/chart.ts
Global CSS theme tokens, layout, timeline and component styles; runtime theme token exports and Chart.js explicit registration/configuration.
Text formatting and utilities
web/src/lib/format.ts, web/src/lib/actions.ts, web/src/lib/popovers.ts
Formatting helpers (numbers, sizes, dates, GitHub URLs) and Svelte actions (portal, position, dragScroll, scrollSpy). Popover adapters forward imperative callers to reactive store state.
State management and root component
web/src/lib/popover-store.svelte.ts, web/src/App.svelte
Svelte store-based popover and tooltip state. App.svelte orchestrates Chart.js configuration, popovers, timeline initialisation, and wires child components.
Dashboard components
web/src/lib/components/Header.svelte, web/src/lib/components/Heatmap.svelte, web/src/lib/components/Table.svelte, web/src/lib/components/OverallCharts.svelte, web/src/lib/components/LangBar.svelte, web/src/lib/components/TechGrid.svelte, web/src/lib/components/YearToggles.svelte
Header, heatmap with year toggles and tooltip, contributor table with popovers, repo-wide charts, language bar with optional GitHub links, technology grid, and year selection controls.
Timeline and interactive popovers
web/src/lib/timeline.ts, web/src/lib/components/AuthorPopover.svelte, web/src/lib/components/CommitPopover.svelte, web/src/lib/components/TimelineTooltip.svelte, web/src/lib/components/PatternCard.svelte, web/src/lib/components/ContributorCard.svelte
Canvas-based commit timeline with zoom/pan/select interactions, minimap, tag strip, author and commit popovers, timeline hover tooltip, pattern cards for hour/day-of-week, and contributor sparkline cards.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: migrating the frontend to modern technologies (Svelte 5, Vite, TypeScript), which aligns with the extensive frontend rewrite documented in the PR summary.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 migrate-frontend-svelte-vite

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

tyom added 2 commits May 22, 2026 12:09
Replace the 1,740-line vanilla-JS template.html with a componentized,
type-safe frontend built with Bun + Vite. The output is still a single
self-contained HTML file embedded into dist/repo-intel by build.py, so the
end-user contract is unchanged: a zero-dependency Python executable. Bun/Vite
are build-time only.

- web/: Svelte 5 + Vite + TypeScript app. vite-plugin-singlefile inlines all
  JS+CSS into web/dist/index.html, which keeps the /*__DATA_INJECTION__*/
  marker (re-inserted post-build by a small Vite plugin) so repo-intel.py's
  injection is unchanged.
- The dashboard engine is split into focused, typed modules under web/src/lib/
  (heatmap, timeline, charts, table, popovers, header, interactions + helpers),
  ported to parity from the old template.
- Chart.js is now bundled instead of loaded from a CDN — the output is fully
  offline/self-contained.
- build.py + repo-intel.py read web/dist/index.html instead of template.html.
- Makefile gains web-build/web-dev; pre-commit hook rebuilds the bundle and
  watches web/ (needs Bun); ci.yml/release.yml add oven-sh/setup-bun pinned to
  1.3.10 for reproducible builds.
- Delete template.html; update README, .gitignore.

Verified end-to-end via python3 dist/repo-intel against this repo and a
3-contributor mock: zero console errors, full visual parity.
Replace `chart.js/auto` with explicit Chart.register() of only the
controllers/elements/scales/plugins this dashboard uses (line, bar, doughnut +
their elements, linear/category scales, Title/Tooltip/Legend/Filler). Drops the
unused controllers (radar, polar-area, scatter, bubble) and the radial scale.

web/dist/index.html: 303.7 kB -> 280.0 kB (gzip 102.5 -> 95.2 kB).

Verified in-browser with the 3-contributor mock: all chart types render with no
"not a registered ..." runtime errors.
@tyom tyom force-pushed the migrate-frontend-svelte-vite branch from 6be679f to 6bb738e Compare May 22, 2026 11:10
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (4)
web/src/lib/heatmap.ts (2)

12-18: ⚡ Quick win

Consider extracting hardcoded background colour to theme.

The hardcoded RGB values { r: 22, g: 27, b: 34 } (line 16) duplicate the background colour from CSS. If the background colour changes in the stylesheet, this value won't update automatically, creating a maintenance burden.

Consider reading the background colour from a CSS custom property or computing it from getComputedStyle.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/heatmap.ts` around lines 12 - 18, The shade function currently
hardcodes the background RGB as { r: 22, g: 27, b: 34 }, which can drift from
the actual CSS background; change shade to read the background color dynamically
(e.g., from a CSS custom property or via getComputedStyle) instead of the
literal object: locate the shade function in web/src/lib/heatmap.ts and replace
the hardcoded bg with a value parsed from a CSS variable (e.g., --app-bg) or
computed style on a root element, convert that CSS color to r/g/b and use those
values in the existing rgb(...) computation so the heatmap always matches the
stylesheet.

86-90: ⚡ Quick win

Consider extracting weekend highlight colour to theme.

The hardcoded rgba(240,170,90,0.05) for weekend highlighting should come from the theme system or a CSS custom property rather than being hardcoded, for consistency with the rest of the colour system.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/heatmap.ts` around lines 86 - 90, The weekend highlight RGBA is
hardcoded in the heatmap cell generation (inside week.forEach where cellBg is
computed); replace the literal "rgba(240,170,90,0.05)" with a theme-driven value
(e.g. a CSS custom property like --heatmap-weekend-highlight or the app's theme
helper) so the weekend overlay comes from the theme system; update the code that
builds cellBg (and any inline style for class "heatmap-cell") to use that CSS
variable or theme accessor with a sensible fallback matching the current RGBA.
web/src/main.ts (1)

6-14: ⚡ Quick win

Consider adding error handling to the bootstrap function.

If loadData() fails or the app mount throws an exception, the user will see a blank page with no indication of what went wrong. Adding a try-catch block with user-visible error messaging would improve debuggability.

🛡️ Suggested error handling
 async function bootstrap() {
-  const data = await loadData();
-  mount(App, {
-    target: document.getElementById("app")!,
-    props: { data },
-  });
+  try {
+    const data = await loadData();
+    mount(App, {
+      target: document.getElementById("app")!,
+      props: { data },
+    });
+  } catch (error) {
+    console.error("Failed to bootstrap application:", error);
+    const app = document.getElementById("app");
+    if (app) {
+      app.innerHTML = `<div style="padding: 2rem; color: `#ef4444`;">
+        <h2>Failed to load dashboard</h2>
+        <pre>${error}</pre>
+      </div>`;
+    }
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/main.ts` around lines 6 - 14, The bootstrap function lacks error
handling for failures from loadData() or mount(App,...); wrap the async body of
bootstrap in a try-catch, call loadData() and mount(App, ...) inside the try,
and in the catch log the error (console.error or a logger) and surface a
user-visible message (e.g., render a simple fallback DOM message or use alert)
so users see a clear failure instead of a blank page; reference the bootstrap
function, loadData, mount, and App when making the change.
web/package.json (1)

1-24: ⚡ Quick win

Consider adding an engines field to specify required runtime versions.

The PR summary mentions Bun 1.3.10 is used in CI and required for contributors touching web/. Adding an engines field would document this requirement and prevent version mismatches.

📝 Suggested addition
   "private": true,
   "version": "0.0.0",
   "type": "module",
+  "engines": {
+    "bun": ">=1.3.10"
+  },
   "scripts": {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/package.json` around lines 1 - 24, Add an "engines" field to
web/package.json to document and enforce required runtimes (e.g., Bun 1.3.10
used in CI) so contributors get version mismatch warnings; update the top-level
JSON object to include an "engines" key with entries like "bun": ">=1.3.10" (and
optionally "node"/"npm"/"pnpm" ranges you support) and ensure any CI/docs
referring to runtimes align with these version constraints.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 20-22: Replace the tag-based action reference and stale Bun
version in the GitHub Actions step that uses oven-sh/setup-bun: change the
action identifier from "oven-sh/setup-bun@v2" to the pinned commit
"oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6" and update the
bun-version value used in that step from "1.3.10" to "1.3.14" so the CI uses the
fixed action commit and the newer Bun release.

In @.github/workflows/release.yml:
- Around line 16-18: Replace the mutable action reference oven-sh/setup-bun@v2
with the immutable commit pin
oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 in the workflow and
keep or update the bun-version key accordingly; specifically change the action
reference string and, if you want to move to a newer stable Bun, update the
bun-version value from 1.3.10 to the chosen stable release (adjust bun-version
in the same step block where oven-sh/setup-bun is referenced).

In `@web/package.json`:
- Around line 12-23: The package.json currently pins "svelte" at "^5.19.0" while
"`@sveltejs/vite-plugin-svelte`@^5.0.0" expects a peer of "svelte ^5.46.4", so
update the dependency to a compatible range (e.g. change the "svelte" entry in
web/package.json to "^5.46.4" or a range that includes 5.46.4) to align with the
plugin; after bumping, run an install and confirm build with existing Vite
config (notably the transformIndexHtml/enforce: "post" usage) to ensure no
runtime/compiler mismatches. Ensure package-lock or yarn.lock is
updated/committed so the resolved version stays consistent.

In `@web/public/mock-data.json`:
- Line 11: Replace the real personal email "inspiro@gmail.com" used in the mock
JSON with a synthetic address (e.g., "tyom@example.com") everywhere it appears;
search for the JSON key "email" and any occurrences of the value
"inspiro@gmail.com" (including the entries referenced in the diff and the
repeated fixtures) and update them consistently so no real PII remains in the
fixture data.

In `@web/src/lib/dashboard.ts`:
- Around line 19-20: Guard the derived ratios c.lc and c.avgPerDay against
division by zero: when computing c.lc (using c.net / c.commits) and c.avgPerDay
(using c.commits / c.activeDays) check that c.commits and c.activeDays are
non-zero (or truthy) before dividing and use a safe fallback (e.g., 0 or null)
when they are zero/undefined; update the expressions around c.lc and c.avgPerDay
to conditionally compute the toFixed(1) result only when the divisor is valid to
avoid Infinity/NaN in UI output.

In `@web/src/lib/format.ts`:
- Line 5: The pct function can produce Infinity/NaN when t is 0; update the
pct(n: number, t: number) implementation to guard for t === 0 (or near-zero) and
return a sensible default string (e.g., "0.0%") instead of performing the
division, otherwise perform the existing calculation and formatting; locate the
pct function in web/src/lib/format.ts and add the zero-check before computing
((n / t) * 100).toFixed(1) + "%".

In `@web/src/styles/app.css`:
- Line 152: The CSS uses the deprecated declaration "word-break: break-word"
(e.g., in the .timeline-tooltip .tt-subject rule) — find every occurrence of
"word-break: break-word" and replace it with a non-deprecated combination: set
"word-break: normal;" and add "overflow-wrap: anywhere;" so long words still
wrap correctly; update each rule (including .timeline-tooltip .tt-subject and
the other locations flagged) to remove the deprecated property and add these two
properties instead.

---

Nitpick comments:
In `@web/package.json`:
- Around line 1-24: Add an "engines" field to web/package.json to document and
enforce required runtimes (e.g., Bun 1.3.10 used in CI) so contributors get
version mismatch warnings; update the top-level JSON object to include an
"engines" key with entries like "bun": ">=1.3.10" (and optionally
"node"/"npm"/"pnpm" ranges you support) and ensure any CI/docs referring to
runtimes align with these version constraints.

In `@web/src/lib/heatmap.ts`:
- Around line 12-18: The shade function currently hardcodes the background RGB
as { r: 22, g: 27, b: 34 }, which can drift from the actual CSS background;
change shade to read the background color dynamically (e.g., from a CSS custom
property or via getComputedStyle) instead of the literal object: locate the
shade function in web/src/lib/heatmap.ts and replace the hardcoded bg with a
value parsed from a CSS variable (e.g., --app-bg) or computed style on a root
element, convert that CSS color to r/g/b and use those values in the existing
rgb(...) computation so the heatmap always matches the stylesheet.
- Around line 86-90: The weekend highlight RGBA is hardcoded in the heatmap cell
generation (inside week.forEach where cellBg is computed); replace the literal
"rgba(240,170,90,0.05)" with a theme-driven value (e.g. a CSS custom property
like --heatmap-weekend-highlight or the app's theme helper) so the weekend
overlay comes from the theme system; update the code that builds cellBg (and any
inline style for class "heatmap-cell") to use that CSS variable or theme
accessor with a sensible fallback matching the current RGBA.

In `@web/src/main.ts`:
- Around line 6-14: The bootstrap function lacks error handling for failures
from loadData() or mount(App,...); wrap the async body of bootstrap in a
try-catch, call loadData() and mount(App, ...) inside the try, and in the catch
log the error (console.error or a logger) and surface a user-visible message
(e.g., render a simple fallback DOM message or use alert) so users see a clear
failure instead of a blank page; reference the bootstrap function, loadData,
mount, and App when making the change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec89ac6d-5afc-4993-a5d9-bd4c0bcd4f9e

📥 Commits

Reviewing files that changed from the base of the PR and between fea2f4e and 6be679f.

⛔ Files ignored due to path filters (2)
  • dist/repo-intel is excluded by !**/dist/**
  • web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (32)
  • .githooks/pre-commit
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .gitignore
  • Makefile
  • README.md
  • build.py
  • repo-intel.py
  • template.html
  • web/.gitignore
  • web/index.html
  • web/package.json
  • web/public/mock-data.json
  • web/src/App.svelte
  • web/src/data.ts
  • web/src/lib/avatar.ts
  • web/src/lib/chart.ts
  • web/src/lib/charts.ts
  • web/src/lib/dashboard.ts
  • web/src/lib/format.ts
  • web/src/lib/header.ts
  • web/src/lib/heatmap.ts
  • web/src/lib/interactions.ts
  • web/src/lib/popovers.ts
  • web/src/lib/table.ts
  • web/src/lib/theme.ts
  • web/src/lib/timeline.ts
  • web/src/main.ts
  • web/src/styles/app.css
  • web/src/types.ts
  • web/tsconfig.json
  • web/vite.config.ts
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.42.2)
web/src/lib/header.ts

[warning] 37-37: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: subtitleEl.innerHTML = ${D.dateRange.start} — ${D.dateRange.end} · ${fmt(totals.commits)} commits · <span style="color:${colorAdded}">+${fmt(totals.added)}</span> <span style="color:${colorDeleted}">-${fmt(totals.deleted)}</span> (net ${subtitleNetHtml}) · ${fmt(totalContribCount)} contributor${totalContribCount === 1 ? "" : "s"}${sizeStr ? · ${sizeStr} : ""}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 58-61: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: langEl.innerHTML = repoLangs.length
? <div class="tech-bar-label">${langLabel}</div> + langBarHtml(repoLangs, { legend: true, repoBase: D.githubBaseUrl })
: <div class="tech-bar-label">Languages</div> +
<div class="tech-empty">Couldn't load language data from the GitHub API for this repo.</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 66-80: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: fwEl.innerHTML =
<div class="tech-bar-label">Frameworks &amp; tools</div> +
(frameworks.length
? <div class="frameworks"> +
frameworks
.map(
(g) =>
<div class="fw-group"> +
<span class="fw-lang"><span class="lang-dot" style="background:${g.color}"></span>${escapeHtml(g.language)}</span> +
<span class="fw-items">${g.names.map(escapeHtml).join(", ")}</span> +
</div>,
)
.join("") +
</div>
: <div class="tech-empty">No known frameworks detected in the repo's dependency manifests.</div>)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 37-37: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: subtitleEl.innerHTML = ${D.dateRange.start} — ${D.dateRange.end} · ${fmt(totals.commits)} commits · <span style="color:${colorAdded}">+${fmt(totals.added)}</span> <span style="color:${colorDeleted}">-${fmt(totals.deleted)}</span> (net ${subtitleNetHtml}) · ${fmt(totalContribCount)} contributor${totalContribCount === 1 ? "" : "s"}${sizeStr ? · ${sizeStr} : ""}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 58-61: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: langEl.innerHTML = repoLangs.length
? <div class="tech-bar-label">${langLabel}</div> + langBarHtml(repoLangs, { legend: true, repoBase: D.githubBaseUrl })
: <div class="tech-bar-label">Languages</div> +
<div class="tech-empty">Couldn't load language data from the GitHub API for this repo.</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 66-80: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: fwEl.innerHTML =
<div class="tech-bar-label">Frameworks &amp; tools</div> +
(frameworks.length
? <div class="frameworks"> +
frameworks
.map(
(g) =>
<div class="fw-group"> +
<span class="fw-lang"><span class="lang-dot" style="background:${g.color}"></span>${escapeHtml(g.language)}</span> +
<span class="fw-items">${g.names.map(escapeHtml).join(", ")}</span> +
</div>,
)
.join("") +
</div>
: <div class="tech-empty">No known frameworks detected in the repo's dependency manifests.</div>)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

web/src/lib/heatmap.ts

[warning] 98-98: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: container.innerHTML = <div class="heatmap-wrap">${monthHtml}<div class="heatmap-grid">${dayLabelsHtml}${gridHtml}</div>${legendHtml}</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 116-116: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: togglesDiv.innerHTML = html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 139-139: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: tooltip.innerHTML = <span class="tt-dot" style="background:${cellColorAttr}"></span><span class="tt-date">${label}</span><span class="tt-count">on ${formatted}</span>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 98-98: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: container.innerHTML = <div class="heatmap-wrap">${monthHtml}<div class="heatmap-grid">${dayLabelsHtml}${gridHtml}</div>${legendHtml}</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 116-116: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: togglesDiv.innerHTML = html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 139-139: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: tooltip.innerHTML = <span class="tt-dot" style="background:${cellColorAttr}"></span><span class="tt-date">${label}</span><span class="tt-count">on ${formatted}</span>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

web/src/lib/table.ts

[warning] 32-32: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: tbody.innerHTML = rowsHtml + subtotalHtml + totalsHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 32-32: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: tbody.innerHTML = rowsHtml + subtotalHtml + totalsHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

web/src/lib/popovers.ts

[warning] 59-68: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: authorPopover.innerHTML =
<div class="lp-header">${avatarHtml}<div class="lp-id"><div class="lp-name">${escapeHtml(c.name)}</div>${handle ? ${escapeHtml(handle)} : ""}</div></div> +
bioHtml +
countsHtml +
metaHtml +
<div class="lp-divider"></div> +
<div class="lp-stats">${fmt(c.commits)} commits · ${c.activeDays} active day${c.activeDays === 1 ? "" : "s"}</div> +
<div class="lp-stats"><span class="add">+${fmt(c.added)}</span> <span class="del">-${fmt(c.deleted)}</span> (net ${netHtml})</div> +
<div class="lp-period">${c.first} — ${c.last}</div> +
langBarHtml(c.languages, { legend: true })
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 165-168: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: commitPopover.innerHTML =
<div class="cp-title" style="color:${clr(colorIdx)}">${escapeHtml(label)}</div> +
<div class="cp-sub">${escapeHtml(c.name)} · ${fmt(list.length)} commit${list.length === 1 ? "" : "s"}</div> +
<div class="cp-list">${rows}</div>${more}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 59-68: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: authorPopover.innerHTML =
<div class="lp-header">${avatarHtml}<div class="lp-id"><div class="lp-name">${escapeHtml(c.name)}</div>${handle ? ${escapeHtml(handle)} : ""}</div></div> +
bioHtml +
countsHtml +
metaHtml +
<div class="lp-divider"></div> +
<div class="lp-stats">${fmt(c.commits)} commits · ${c.activeDays} active day${c.activeDays === 1 ? "" : "s"}</div> +
<div class="lp-stats"><span class="add">+${fmt(c.added)}</span> <span class="del">-${fmt(c.deleted)}</span> (net ${netHtml})</div> +
<div class="lp-period">${c.first} — ${c.last}</div> +
langBarHtml(c.languages, { legend: true })
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 165-168: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: commitPopover.innerHTML =
<div class="cp-title" style="color:${clr(colorIdx)}">${escapeHtml(label)}</div> +
<div class="cp-sub">${escapeHtml(c.name)} · ${fmt(list.length)} commit${list.length === 1 ? "" : "s"}</div> +
<div class="cp-list">${rows}</div>${more}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

web/src/lib/charts.ts

[warning] 114-114: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: card.innerHTML = <div class="rank">#${i + 1}</div><div class="name" style="color:${clr(i)}">${escapeHtml(c.name)}</div><div class="meta"><span>${fmt(c.commits)} commits</span><span class="add">${fmt(c.added)} ++</span><span class="del">${fmt(c.deleted)} --</span></div><canvas id="contrib-${i}"></canvas>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 155-155: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: div.innerHTML = <canvas id="${idPrefix}-${i}"></canvas>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 114-114: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: card.innerHTML = <div class="rank">#${i + 1}</div><div class="name" style="color:${clr(i)}">${escapeHtml(c.name)}</div><div class="meta"><span>${fmt(c.commits)} commits</span><span class="add">${fmt(c.added)} ++</span><span class="del">${fmt(c.deleted)} --</span></div><canvas id="contrib-${i}"></canvas>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 155-155: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: div.innerHTML = <canvas id="${idPrefix}-${i}"></canvas>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

web/src/lib/timeline.ts

[warning] 84-84: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: labelsDiv.innerHTML = labelsHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 426-426: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: yearsBar.innerHTML = buildYearsInner(currentWidth)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 437-437: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: oldAxis.outerHTML = newAxis
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 923-926: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: tooltip.innerHTML =
<div class="tt-author-row">${avatarHtml}<span class="tt-author">${escapeHtml(author.name)}</span>${headerExtra}</div> +
body +
ftypesHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 1202-1205: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: tooltip.innerHTML =
<div class="tt-author-row"><span class="tt-tag-icon"></span><span class="tt-tag-kicker">TAG</span><span class="tt-tag-name">${escapeHtml(t.name || "")}</span></div> +
(showMsg ? <div class="tt-subject">${escapeHtml(msg)}</div> : "") +
<div class="tt-meta">${escapeHtml(dateStr || t.date || "")}${timeStr ? " " + escapeHtml(timeStr) : ""}${oidShort ? ' · <span class="tt-hash">' + escapeHtml(oidShort) + "</span>" : ""}</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 84-84: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: labelsDiv.innerHTML = labelsHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 426-426: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: yearsBar.innerHTML = buildYearsInner(currentWidth)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 437-437: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: oldAxis.outerHTML = newAxis
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 923-926: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: tooltip.innerHTML =
<div class="tt-author-row">${avatarHtml}<span class="tt-author">${escapeHtml(author.name)}</span>${headerExtra}</div> +
body +
ftypesHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 1202-1205: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: tooltip.innerHTML =
<div class="tt-author-row"><span class="tt-tag-icon"></span><span class="tt-tag-kicker">TAG</span><span class="tt-tag-name">${escapeHtml(t.name || "")}</span></div> +
(showMsg ? <div class="tt-subject">${escapeHtml(msg)}</div> : "") +
<div class="tt-meta">${escapeHtml(dateStr || t.date || "")}${timeStr ? " " + escapeHtml(timeStr) : ""}${oidShort ? ' · <span class="tt-hash">' + escapeHtml(oidShort) + "</span>" : ""}</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

🪛 OpenGrep (1.21.0)
web/src/lib/format.ts

[ERROR] 46-46: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 47-47: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 83-83: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

web/src/lib/popovers.ts

[ERROR] 105-105: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🪛 Stylelint (17.11.1)
web/src/styles/app.css

[error] 152-152: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)


[error] 159-159: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)


[error] 181-181: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)


[error] 183-183: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)

🪛 zizmor (1.25.2)
.github/workflows/ci.yml

[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

.github/workflows/release.yml

[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 16-16: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🔇 Additional comments (26)
web/src/lib/format.ts (1)

1-4: LGTM!

Also applies to: 7-116

web/src/lib/chart.ts (1)

1-41: LGTM!

web/src/lib/charts.ts (1)

1-250: LGTM!

web/src/lib/timeline.ts (1)

1-1332: LGTM!

web/src/lib/table.ts (1)

1-47: LGTM!

web/src/lib/popovers.ts (1)

1-210: LGTM!

web/src/lib/interactions.ts (1)

1-128: LGTM!

web/src/types.ts (1)

1-105: LGTM!

web/src/lib/avatar.ts (1)

1-16: LGTM!

web/src/data.ts (1)

1-16: LGTM!

web/src/App.svelte (1)

1-121: LGTM!

web/src/lib/theme.ts (1)

1-28: LGTM!

web/src/lib/header.ts (2)

7-46: LGTM!


49-83: LGTM!

web/src/lib/heatmap.ts (1)

20-100: LGTM!

Also applies to: 105-126, 128-170

.githooks/pre-commit (1)

17-17: LGTM!

Also applies to: 40-43

.gitignore (1)

1-8: LGTM!

Makefile (1)

6-10: LGTM!

Also applies to: 12-12, 18-18, 25-25

README.md (1)

209-245: LGTM!

build.py (1)

26-32: LGTM!

Also applies to: 43-43

repo-intel.py (1)

1935-1940: LGTM!

web/.gitignore (1)

1-2: LGTM!

web/index.html (1)

1-15: LGTM!

web/tsconfig.json (1)

1-16: LGTM!

web/vite.config.ts (1)

12-24: Check transformIndexHtml ordering semantics in Vite 6 (enforce vs hook order) in web/vite.config.ts (lines 12–24).

transformIndexHtml: { order: "post" } and plugin-level enforce: "post" are both valid and control different execution ordering dimensions in Vite 6; however, Vite 6 viteSingleFile() can also affect HTML transforms—confirm your plugin’s execution is after viteSingleFile() based on how viteSingleFile() is configured/ordered in the plugins array.

web/src/main.ts (1)

2-2: Svelte 5.19 mount API usage matches the documented signatureweb/src/main.ts imports mount from "svelte" and calls mount(App, { target: document.getElementById("app")!, props: { data } }), which aligns with Svelte 5’s mount(component, { target, props, ... }) signature.

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/release.yml
Comment thread web/package.json
Comment thread web/public/mock-data.json Outdated
Comment thread web/src/lib/dashboard.ts Outdated
Comment thread web/src/lib/format.ts Outdated
Comment thread web/src/styles/app.css Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.githooks/pre-commit (1)

26-32: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle staged filenames safely in the unstaged-check loop (Line 26).

for f in $staged is whitespace-splitting and can mis-handle valid filenames. Use NUL-delimited output from git and read safely.

Proposed fix
-for f in $staged; do
-  if ! git diff --quiet -- "$f"; then
-    echo "pre-commit: '$f' has unstaged changes; the rebuild would embed them." >&2
-    echo "  Stage them (git add $f) or stash, then commit again." >&2
-    exit 1
-  fi
-done
+git diff --cached --name-only -z -- $sources | while IFS= read -r -d '' f; do
+  if ! git diff --quiet -- "$f"; then
+    echo "pre-commit: '$f' has unstaged changes; the rebuild would embed them." >&2
+    echo "  Stage them (git add \"$f\") or stash, then commit again." >&2
+    exit 1
+  fi
+done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.githooks/pre-commit around lines 26 - 32, The loop using "for f in $staged"
splits filenames on whitespace and breaks on names with spaces/newlines; change
to use NUL-delimited git output and a safe read loop: produce staged with git
(e.g., git diff --name-only --cached -z) and iterate with while IFS= read -r -d
'' f; do ... done, keeping the existing git diff --quiet -- "$f" check and
preserving the error messages but quoting "$f" where used.
♻️ Duplicate comments (3)
.github/workflows/ci.yml (1)

20-22: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use an immutable commit pin for oven-sh/setup-bun.

Line 20 uses a mutable major tag. For CI supply-chain hardening and reproducibility, pin this action to a commit SHA.

#!/bin/bash
set -euo pipefail

echo "Current ci.yml reference:"
awk 'NR>=18 && NR<=24 {printf "%5d  %s\n", NR, $0}' .github/workflows/ci.yml

echo
echo "Resolve oven-sh/setup-bun tag v2 to its current commit SHA:"
curl -sS -H "Accept: application/vnd.github+json" \
  https://api.github.com/repos/oven-sh/setup-bun/git/ref/tags/v2 \
  | jq -r '.object.sha'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 20 - 22, The workflow is using the
mutable tag "oven-sh/setup-bun@v2" which is not pinned to a commit SHA; replace
the action reference with the resolved immutable commit SHA (e.g.,
oven-sh/setup-bun@<full-commit-sha>) to harden CI and ensure reproducible runs.
Locate the usage of the action string "oven-sh/setup-bun@v2" in the CI workflow
and update it to the corresponding commit SHA returned by the GitHub API (use
the tag v2 to find its object.sha) so the workflow references the exact commit
instead of a floating tag. Ensure the bun-version input remains unchanged.
.github/workflows/release.yml (1)

16-18: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin oven-sh/setup-bun to an immutable commit SHA.

Line 16 currently tracks a mutable tag (@v2), which weakens release-chain integrity. Please pin to a full commit digest.

#!/bin/bash
set -euo pipefail

echo "Current release.yml reference:"
awk 'NR>=14 && NR<=20 {printf "%5d  %s\n", NR, $0}' .github/workflows/release.yml

echo
echo "Resolve oven-sh/setup-bun tag v2 to its current commit SHA:"
curl -sS -H "Accept: application/vnd.github+json" \
  https://api.github.com/repos/oven-sh/setup-bun/git/ref/tags/v2 \
  | jq -r '.object.sha'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 16 - 18, Replace the mutable tag
for the GitHub Action in your workflow: change the uses entry from
"oven-sh/setup-bun@v2" to the same action pinned to the full commit SHA (e.g.
"oven-sh/setup-bun@<full-commit-sha>"). Resolve the v2 tag to its current commit
SHA using the GitHub API (or `git ls-remote`), then update the release
workflow's uses line with that full SHA to ensure the action is immutable and
repeatable.
web/src/lib/dashboard.ts (1)

19-20: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard ratio calculations against zero divisors.

Line 19 and Line 20 still allow Infinity/NaN when commits or activeDays is 0. Please keep these derived values bounded before formatting.

Proposed fix
-    c.lc = +(c.net / c.commits).toFixed(1);
-    c.avgPerDay = +(c.commits / c.activeDays).toFixed(1);
+    c.lc = c.commits > 0 ? +(c.net / c.commits).toFixed(1) : 0;
+    c.avgPerDay = c.activeDays > 0 ? +(c.commits / c.activeDays).toFixed(1) : 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/dashboard.ts` around lines 19 - 20, The ratio calculations for
c.lc and c.avgPerDay can produce Infinity/NaN when c.commits or c.activeDays is
0; update the logic that sets c.lc and c.avgPerDay to first check the divisor
(c.commits and c.activeDays respectively) and only perform the division and
.toFixed(1) when the divisor is truthy/non-zero, otherwise assign a safe
fallback (e.g., 0 or null) so the values are bounded before formatting; locate
and change the lines that set c.lc and c.avgPerDay in the dashboard code to
implement these guards.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build.py`:
- Around line 24-25: The file reads/writes text using
Path.read_text()/write_text() without an explicit encoding which can produce
non-deterministic results; update all usages (e.g., where you assign script =
(src_dir / "repo-intel.py").read_text(), and the other occurrences at the
locations noted) to call read_text(encoding="utf-8") and write_text(...,
encoding="utf-8") so all text IO is explicitly UTF-8 encoded for deterministic
cross-platform bundling.

In `@web/package.json`:
- Around line 13-16: The svelte dependency declared as "svelte": "^5.19.0" is
incompatible with `@sveltejs/vite-plugin-svelte`@^5.0.0's peer requirement; update
the "svelte" entry to "^5.46.4" (or newer) in package.json so it satisfies
`@sveltejs/vite-plugin-svelte`, then reinstall and regenerate your lockfile
(npm/yarn/pnpm) to pin the resolved version; verify the package.json entry for
"svelte" and the lockfile reflect the new version and run a build to confirm no
peer conflicts.

In `@web/src/lib/header.ts`:
- Around line 73-76: The template injects g.color directly into an inline style
in the header renderer (the code block that builds the `<span class="fw-lang">`
with `style="background:${g.color}"`), which is unsafe; add a strict validator
function (e.g., isValidHexRgbHslColor) and use it to gate g.color before
interpolation—if validation fails, substitute a safe default color or omit the
style attribute entirely; update the code that builds the string (the mapping
that references g.color) to call the validator and only include `background:`
when valid.

In `@web/src/lib/popovers.ts`:
- Line 68: The template string that assigns to innerHTML is inserting c.first
and c.last raw (the line with `<div class="lp-period">${c.first} —
${c.last}</div>`); escape both values before concatenation to prevent HTML
injection by creating/using an HTML-escaping helper (e.g., escapeHtml) and
replace `${c.first}` and `${c.last}` with escaped versions when building the
innerHTML string so only safe text is injected.
- Line 159: Construct the commit URL into a local variable first (e.g., const
commitUrl = `${D.githubBaseUrl}/commit/${encodeURIComponent(c2.h)}`) and then
HTML-escape that variable before interpolating into the template string for the
anchor href; update the code around the template literal that currently uses
`${D.githubBaseUrl}/commit/${encodeURIComponent(c2.h)}` to use the escaped value
(e.g., escapedCommitUrl). If there is a shared utility (like an existing
escapeHtml/htmlEscape), reuse it; otherwise add a small escapeHtml function that
replaces & < > " ' with entities and call it when building the href in the
popovers.ts function that renders the commit row.

In `@web/src/lib/table.ts`:
- Line 28: The href attribute is being injected directly from authorUrl(D, c)
which can lead to attribute injection; update the template in the function that
returns the table row so the URL is safely escaped/encoded before insertion
(e.g. replace href="${authorUrl(D, c)}" with href="${escapeHtml(authorUrl(D,
c))}" or use a URL-encoding helper like encodeURI/encodeURIComponent on
authorUrl's output), ensuring you call the existing escape/encoding utility
(escapeHtml or equivalent) where the template is constructed so authorUrl(D, c)
is sanitized before rendering.
- Line 32: The totals row currently divides totals.net by totals.commits in the
expression +(totals.net! / totals.commits).toFixed(1) which can yield
Infinity/NaN when totals.commits is zero; update the construction of totalsHtml
in web/src/lib/table.ts to guard that division by checking totals.commits (or
totals.commits === 0) and return a safe fallback (e.g., "0" or "-" as a string
or 0 as a number formatted via fmt) when commits is zero, otherwise perform the
division and toFixed(1); adjust the expression inside the template literal so
the fallback is used instead of the raw division.

---

Outside diff comments:
In @.githooks/pre-commit:
- Around line 26-32: The loop using "for f in $staged" splits filenames on
whitespace and breaks on names with spaces/newlines; change to use NUL-delimited
git output and a safe read loop: produce staged with git (e.g., git diff
--name-only --cached -z) and iterate with while IFS= read -r -d '' f; do ...
done, keeping the existing git diff --quiet -- "$f" check and preserving the
error messages but quoting "$f" where used.

---

Duplicate comments:
In @.github/workflows/ci.yml:
- Around line 20-22: The workflow is using the mutable tag
"oven-sh/setup-bun@v2" which is not pinned to a commit SHA; replace the action
reference with the resolved immutable commit SHA (e.g.,
oven-sh/setup-bun@<full-commit-sha>) to harden CI and ensure reproducible runs.
Locate the usage of the action string "oven-sh/setup-bun@v2" in the CI workflow
and update it to the corresponding commit SHA returned by the GitHub API (use
the tag v2 to find its object.sha) so the workflow references the exact commit
instead of a floating tag. Ensure the bun-version input remains unchanged.

In @.github/workflows/release.yml:
- Around line 16-18: Replace the mutable tag for the GitHub Action in your
workflow: change the uses entry from "oven-sh/setup-bun@v2" to the same action
pinned to the full commit SHA (e.g. "oven-sh/setup-bun@<full-commit-sha>").
Resolve the v2 tag to its current commit SHA using the GitHub API (or `git
ls-remote`), then update the release workflow's uses line with that full SHA to
ensure the action is immutable and repeatable.

In `@web/src/lib/dashboard.ts`:
- Around line 19-20: The ratio calculations for c.lc and c.avgPerDay can produce
Infinity/NaN when c.commits or c.activeDays is 0; update the logic that sets
c.lc and c.avgPerDay to first check the divisor (c.commits and c.activeDays
respectively) and only perform the division and .toFixed(1) when the divisor is
truthy/non-zero, otherwise assign a safe fallback (e.g., 0 or null) so the
values are bounded before formatting; locate and change the lines that set c.lc
and c.avgPerDay in the dashboard code to implement these guards.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f1d071b-20a7-49ca-b7c3-9656b20d6e70

📥 Commits

Reviewing files that changed from the base of the PR and between 6be679f and 6bb738e.

⛔ Files ignored due to path filters (2)
  • dist/repo-intel is excluded by !**/dist/**
  • web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (32)
  • .githooks/pre-commit
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .gitignore
  • Makefile
  • README.md
  • build.py
  • repo-intel.py
  • template.html
  • web/.gitignore
  • web/index.html
  • web/package.json
  • web/public/mock-data.json
  • web/src/App.svelte
  • web/src/data.ts
  • web/src/lib/avatar.ts
  • web/src/lib/chart.ts
  • web/src/lib/charts.ts
  • web/src/lib/dashboard.ts
  • web/src/lib/format.ts
  • web/src/lib/header.ts
  • web/src/lib/heatmap.ts
  • web/src/lib/interactions.ts
  • web/src/lib/popovers.ts
  • web/src/lib/table.ts
  • web/src/lib/theme.ts
  • web/src/lib/timeline.ts
  • web/src/main.ts
  • web/src/styles/app.css
  • web/src/types.ts
  • web/tsconfig.json
  • web/vite.config.ts
✅ Files skipped from review due to trivial changes (5)
  • web/.gitignore
  • web/index.html
  • repo-intel.py
  • .gitignore
  • README.md
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.42.2)
web/src/lib/header.ts

[warning] 37-37: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: subtitleEl.innerHTML = ${D.dateRange.start} — ${D.dateRange.end} · ${fmt(totals.commits)} commits · <span style="color:${colorAdded}">+${fmt(totals.added)}</span> <span style="color:${colorDeleted}">-${fmt(totals.deleted)}</span> (net ${subtitleNetHtml}) · ${fmt(totalContribCount)} contributor${totalContribCount === 1 ? "" : "s"}${sizeStr ? · ${sizeStr} : ""}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 58-61: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: langEl.innerHTML = repoLangs.length
? <div class="tech-bar-label">${langLabel}</div> + langBarHtml(repoLangs, { legend: true, repoBase: D.githubBaseUrl })
: <div class="tech-bar-label">Languages</div> +
<div class="tech-empty">Couldn't load language data from the GitHub API for this repo.</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 66-80: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: fwEl.innerHTML =
<div class="tech-bar-label">Frameworks &amp; tools</div> +
(frameworks.length
? <div class="frameworks"> +
frameworks
.map(
(g) =>
<div class="fw-group"> +
<span class="fw-lang"><span class="lang-dot" style="background:${g.color}"></span>${escapeHtml(g.language)}</span> +
<span class="fw-items">${g.names.map(escapeHtml).join(", ")}</span> +
</div>,
)
.join("") +
</div>
: <div class="tech-empty">No known frameworks detected in the repo's dependency manifests.</div>)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 37-37: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: subtitleEl.innerHTML = ${D.dateRange.start} — ${D.dateRange.end} · ${fmt(totals.commits)} commits · <span style="color:${colorAdded}">+${fmt(totals.added)}</span> <span style="color:${colorDeleted}">-${fmt(totals.deleted)}</span> (net ${subtitleNetHtml}) · ${fmt(totalContribCount)} contributor${totalContribCount === 1 ? "" : "s"}${sizeStr ? · ${sizeStr} : ""}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 58-61: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: langEl.innerHTML = repoLangs.length
? <div class="tech-bar-label">${langLabel}</div> + langBarHtml(repoLangs, { legend: true, repoBase: D.githubBaseUrl })
: <div class="tech-bar-label">Languages</div> +
<div class="tech-empty">Couldn't load language data from the GitHub API for this repo.</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 66-80: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: fwEl.innerHTML =
<div class="tech-bar-label">Frameworks &amp; tools</div> +
(frameworks.length
? <div class="frameworks"> +
frameworks
.map(
(g) =>
<div class="fw-group"> +
<span class="fw-lang"><span class="lang-dot" style="background:${g.color}"></span>${escapeHtml(g.language)}</span> +
<span class="fw-items">${g.names.map(escapeHtml).join(", ")}</span> +
</div>,
)
.join("") +
</div>
: <div class="tech-empty">No known frameworks detected in the repo's dependency manifests.</div>)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

web/src/lib/popovers.ts

[warning] 59-68: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: authorPopover.innerHTML =
<div class="lp-header">${avatarHtml}<div class="lp-id"><div class="lp-name">${escapeHtml(c.name)}</div>${handle ? ${escapeHtml(handle)} : ""}</div></div> +
bioHtml +
countsHtml +
metaHtml +
<div class="lp-divider"></div> +
<div class="lp-stats">${fmt(c.commits)} commits · ${c.activeDays} active day${c.activeDays === 1 ? "" : "s"}</div> +
<div class="lp-stats"><span class="add">+${fmt(c.added)}</span> <span class="del">-${fmt(c.deleted)}</span> (net ${netHtml})</div> +
<div class="lp-period">${c.first} — ${c.last}</div> +
langBarHtml(c.languages, { legend: true })
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 165-168: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: commitPopover.innerHTML =
<div class="cp-title" style="color:${clr(colorIdx)}">${escapeHtml(label)}</div> +
<div class="cp-sub">${escapeHtml(c.name)} · ${fmt(list.length)} commit${list.length === 1 ? "" : "s"}</div> +
<div class="cp-list">${rows}</div>${more}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 59-68: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: authorPopover.innerHTML =
<div class="lp-header">${avatarHtml}<div class="lp-id"><div class="lp-name">${escapeHtml(c.name)}</div>${handle ? ${escapeHtml(handle)} : ""}</div></div> +
bioHtml +
countsHtml +
metaHtml +
<div class="lp-divider"></div> +
<div class="lp-stats">${fmt(c.commits)} commits · ${c.activeDays} active day${c.activeDays === 1 ? "" : "s"}</div> +
<div class="lp-stats"><span class="add">+${fmt(c.added)}</span> <span class="del">-${fmt(c.deleted)}</span> (net ${netHtml})</div> +
<div class="lp-period">${c.first} — ${c.last}</div> +
langBarHtml(c.languages, { legend: true })
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 165-168: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: commitPopover.innerHTML =
<div class="cp-title" style="color:${clr(colorIdx)}">${escapeHtml(label)}</div> +
<div class="cp-sub">${escapeHtml(c.name)} · ${fmt(list.length)} commit${list.length === 1 ? "" : "s"}</div> +
<div class="cp-list">${rows}</div>${more}
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

web/src/lib/charts.ts

[warning] 114-114: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: card.innerHTML = <div class="rank">#${i + 1}</div><div class="name" style="color:${clr(i)}">${escapeHtml(c.name)}</div><div class="meta"><span>${fmt(c.commits)} commits</span><span class="add">${fmt(c.added)} ++</span><span class="del">${fmt(c.deleted)} --</span></div><canvas id="contrib-${i}"></canvas>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 155-155: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: div.innerHTML = <canvas id="${idPrefix}-${i}"></canvas>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 114-114: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: card.innerHTML = <div class="rank">#${i + 1}</div><div class="name" style="color:${clr(i)}">${escapeHtml(c.name)}</div><div class="meta"><span>${fmt(c.commits)} commits</span><span class="add">${fmt(c.added)} ++</span><span class="del">${fmt(c.deleted)} --</span></div><canvas id="contrib-${i}"></canvas>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 155-155: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: div.innerHTML = <canvas id="${idPrefix}-${i}"></canvas>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

web/src/lib/table.ts

[warning] 32-32: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: tbody.innerHTML = rowsHtml + subtotalHtml + totalsHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 32-32: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: tbody.innerHTML = rowsHtml + subtotalHtml + totalsHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

web/src/lib/heatmap.ts

[warning] 98-98: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: container.innerHTML = <div class="heatmap-wrap">${monthHtml}<div class="heatmap-grid">${dayLabelsHtml}${gridHtml}</div>${legendHtml}</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 116-116: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: togglesDiv.innerHTML = html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 139-139: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: tooltip.innerHTML = <span class="tt-dot" style="background:${cellColorAttr}"></span><span class="tt-date">${label}</span><span class="tt-count">on ${formatted}</span>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 98-98: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: container.innerHTML = <div class="heatmap-wrap">${monthHtml}<div class="heatmap-grid">${dayLabelsHtml}${gridHtml}</div>${legendHtml}</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 116-116: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: togglesDiv.innerHTML = html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 139-139: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: tooltip.innerHTML = <span class="tt-dot" style="background:${cellColorAttr}"></span><span class="tt-date">${label}</span><span class="tt-count">on ${formatted}</span>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

web/src/lib/timeline.ts

[warning] 84-84: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: labelsDiv.innerHTML = labelsHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 426-426: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: yearsBar.innerHTML = buildYearsInner(currentWidth)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 437-437: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: oldAxis.outerHTML = newAxis
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 923-926: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: tooltip.innerHTML =
<div class="tt-author-row">${avatarHtml}<span class="tt-author">${escapeHtml(author.name)}</span>${headerExtra}</div> +
body +
ftypesHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 1202-1205: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: tooltip.innerHTML =
<div class="tt-author-row"><span class="tt-tag-icon"></span><span class="tt-tag-kicker">TAG</span><span class="tt-tag-name">${escapeHtml(t.name || "")}</span></div> +
(showMsg ? <div class="tt-subject">${escapeHtml(msg)}</div> : "") +
<div class="tt-meta">${escapeHtml(dateStr || t.date || "")}${timeStr ? " " + escapeHtml(timeStr) : ""}${oidShort ? ' · <span class="tt-hash">' + escapeHtml(oidShort) + "</span>" : ""}</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 84-84: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: labelsDiv.innerHTML = labelsHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 426-426: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: yearsBar.innerHTML = buildYearsInner(currentWidth)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 437-437: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: oldAxis.outerHTML = newAxis
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 923-926: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: tooltip.innerHTML =
<div class="tt-author-row">${avatarHtml}<span class="tt-author">${escapeHtml(author.name)}</span>${headerExtra}</div> +
body +
ftypesHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 1202-1205: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: tooltip.innerHTML =
<div class="tt-author-row"><span class="tt-tag-icon"></span><span class="tt-tag-kicker">TAG</span><span class="tt-tag-name">${escapeHtml(t.name || "")}</span></div> +
(showMsg ? <div class="tt-subject">${escapeHtml(msg)}</div> : "") +
<div class="tt-meta">${escapeHtml(dateStr || t.date || "")}${timeStr ? " " + escapeHtml(timeStr) : ""}${oidShort ? ' · <span class="tt-hash">' + escapeHtml(oidShort) + "</span>" : ""}</div>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

🪛 OpenGrep (1.21.0)
web/src/lib/format.ts

[ERROR] 46-46: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 47-47: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 83-83: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

web/src/lib/popovers.ts

[ERROR] 105-105: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🪛 Stylelint (17.11.1)
web/src/styles/app.css

[error] 152-152: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)


[error] 159-159: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)


[error] 181-181: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)


[error] 183-183: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)

🪛 zizmor (1.25.2)
.github/workflows/release.yml

[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 16-16: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

.github/workflows/ci.yml

[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🔇 Additional comments (17)
web/src/data.ts (1)

6-15: LGTM!

web/tsconfig.json (1)

1-16: LGTM!

web/src/lib/theme.ts (1)

5-27: LGTM!

web/src/App.svelte (1)

1-120: LGTM!

web/src/lib/chart.ts (1)

7-40: LGTM!

web/vite.config.ts (1)

12-28: LGTM!

web/src/main.ts (1)

6-14: LGTM!

web/src/lib/avatar.ts (1)

5-15: LGTM!

web/public/mock-data.json (1)

10-86: LGTM!

Makefile (1)

6-25: LGTM!

web/src/lib/format.ts (1)

5-5: pct still needs a zero-denominator guard (Line 5).

This is still the same unresolved divide-by-zero path previously reported (Infinity%/NaN% output when t === 0).

web/src/styles/app.css (1)

152-152: Deprecated word-break: break-word usage remains in multiple rules.

This is the same unresolved deprecation already reported for these selectors.

Also applies to: 159-159, 181-181, 183-183

web/src/lib/interactions.ts (1)

4-67: LGTM!

Also applies to: 69-127

web/src/types.ts (1)

1-105: LGTM!

web/src/lib/charts.ts (1)

14-250: LGTM!

web/src/lib/heatmap.ts (1)

1-171: LGTM!

web/src/lib/timeline.ts (1)

1-1332: LGTM!

Comment thread build.py Outdated
Comment thread web/package.json
Comment thread web/src/lib/header.ts Outdated
Comment thread web/src/lib/popovers.ts Outdated
Comment thread web/src/lib/popovers.ts Outdated
Comment thread web/src/lib/table.ts Outdated
Comment thread web/src/lib/table.ts Outdated
tyom added 15 commits May 22, 2026 12:49
- format.ts: pct() returns 0.0% instead of NaN% when total is 0 (a
  repo with no deletions or net==0 hits this in the contributor table)
- app.css: replace deprecated `word-break: break-word` with
  `word-break: normal; overflow-wrap: anywhere` (4 rules)
- main.ts: wrap bootstrap() in try/catch so a data-load failure shows a
  message instead of a blank page
- rebuild dist/repo-intel
Replace the imperative renderTable() (innerHTML string-building + event
delegation) with lib/components/Table.svelte:

- {#each} rows with $derived subtotals/totals; names auto-escaped by Svelte
  (drops the hand-rolled escapeHtml calls)
- per-row onmouseenter/onmouseleave wiring instead of tbody event delegation
  + data-idx + getElementById; native mouseleave removes the relatedTarget
  flicker guard
- author popover lifted to App.svelte and passed into both Table and
  initDashboard, so both halves share the one body-appended singleton

table.ts keeps only topCommitSubtotal (still used by charts.ts).
Replace renderHeader() (getElementById + innerHTML + createElement) with
lib/components/Header.svelte:

- title renders as a GitHub link or plain text via {#if}; document.title set
  in an $effect
- subtitle stat line built in markup with the colored +/- spans; net derived
  locally instead of read from initDashboard's mutation
- timeline heading's ": <duration>" suffix moved to App.svelte (derived from
  fmtTimelineDuration), dropping the .timeline-h querySelector

header.ts now only renders the Technologies section (renderTech); its theme /
fmt / fmtSize / fmtTimelineDuration imports are gone.
Replace renderTech() (getElementById + innerHTML + the hidden/reveal dance)
with lib/components/TechGrid.svelte plus a reusable LangBar.svelte:

- TechGrid renders the languages-by-size/churn label, the stacked LangBar, and
  the framework groups via {#each}; framework/language names auto-escaped
- LangBar is the component form of langBarHtml(): stacked segments + optional
  legend with per-language GitHub code-search links
- the section always renders (with fallbacks for missing GitHub data), so the
  initial `hidden` attribute and the reveal logic are gone

langBarHtml() stays in format.ts for now — the still-imperative author popover
renders it inline; it can go once the popover is a component. header.ts is
fully converted and deleted; dashboard.ts no longer references it.
initHeatmap now returns a rebuildHeatmap(mode) closure instead of
wiring the toggles itself; initDashboard threads it out via a
DashboardBridges object. The new YearToggles.svelte owns the year list
and active state (class:active, per-button onclick), and App passes
rebuildHeatmap as its onSelect callback. Drops the buildYearToggles
IIFE, its getElementById('yearToggles'), and the data-mode delegation.
Each card's rank/name/meta markup becomes ContributorCard.svelte, with
its weekly-commits sparkline staying imperative on a bind:this canvas
created in onMount (and destroyed on unmount). App renders one per
contributor in the grid-5; the #contributorCards id and the
innerHTML/escapeHtml/getElementById block in charts.ts are gone.

Since child onMount fires before parent onMount, configureCharts() moves
to App's top-level script so Chart.js defaults are themed before any
card sparkline is built. Drops now-unused fmt/escapeHtml imports from
charts.ts and the configureCharts call from initDashboard.
The two body-portaled popovers become AuthorPopover.svelte and
CommitPopover.svelte, rendering auto-escaped markup (no more
innerHTML/escapeHtml) and reusing LangBar for the language bar. Their
imperative DOM behaviors are expressed as element directives: use:portal
relocates the node to <body> (so position:fixed isn't trapped by a
transform ancestor) and use:position measures-then-places while owning
the .visible class (no stale-position flash, no flushSync). The avatar
fallback is now an inline onerror + $state flag, reset on contributor
change so a reused <img> node can't strand the next avatar.

State lives in popover-store.svelte.ts ($state); popovers.ts is now thin
adapters writing that store, keeping the createAuthorPopover/
createCommitPopover API and {show,hide,commitsInBucket,dowFull} shape so
timeline.ts and charts.ts call sites are unchanged. langBarHtml is
deleted from format.ts (its last consumer is gone); escapeHtml stays
(still used by the imperative timeline) and avatar.ts stays (timeline's
hover tooltip still uses installAvatarFallback).
Port buildHeatmap() out of the imperative lib/heatmap.ts into
Heatmap.svelte: the calendar grid is now reactive markup ($derived.by
on {mode}) instead of an innerHTML string, and the hover tooltip is a
single body-portaled node positioned with the shared portal/position
actions (like the popovers) rather than a hand-rolled body-appended div
with mouseover/mouseout delegation.

- {mode} drives the grid rebuild reactively, so the year toggles no
  longer need the imperative rebuild closure. App owns heatmapMode state
  and YearToggles sets it; the DashboardBridges/rebuildHeatmap bridge and
  the allDaily aggregation are gone (the component derives allDaily from
  data.dailyData itself).
- Cells are auto-escaped Svelte markup with per-cell mouseenter/mouseleave
  (guarded against out-of-order dispatch) instead of data-* attributes +
  event delegation. Added aria-label per cell (an a11y win over the old
  empty <a> squares).
- Mode type moved to types.ts (the only thing left in heatmap.ts after
  gutting it); lib/heatmap.ts deleted.

svelte-check clean; dist rebuilt. Verified in-browser: year toggle
rebuilds the grid (Current 52-week window ↔ full calendar year), hover
tooltip shows correct date/count/color and flips side, hides on leave.
Define three source aliases, mirrored in vite.config.ts (resolve.alias)
and tsconfig.json (paths) so both the bundler and svelte-check resolve
them:

  $types        -> src/types.ts
  $lib/*        -> src/lib/*
  $components/* -> src/lib/components/*

Rewrite the cross-directory relative imports to use them, e.g.
`../../types` -> `$types`, `../theme` -> `$lib/theme`, and App's
`./lib/components/X.svelte` -> `$components/X.svelte`. Same-directory
sibling imports (`./timeline`, `./LangBar.svelte`) are left as-is since
they're already clean.

Source-only change: the emitted bundle is byte-identical (same JS/CSS
hashes, unchanged dist/repo-intel). svelte-check clean.
Move component-exclusive CSS out of the global app.css into each Svelte
component's <style> block (multiline + nested): YearToggles, Heatmap,
AuthorPopover, CommitPopover, ContributorCard, LangBar, TechGrid. Cross-
component LangBar overrides live in AuthorPopover via :global(). Layout,
typography, scrollbars, and imperatively-rendered .timeline-*/.chart-*
stay global.

Consolidate repeated colour literals into :root vars (--bg-popover,
--bg-tooltip, --grid-line, --selection-fill/stroke, --accent-weekend) and
fix hex values that duplicated existing vars. Canvas/JS code reads the new
vars through theme.ts; Heatmap shade() now interpolates from --bg-card.

Also restore the heatmap cell hover outline: the selector was gated on
.heatmap-cell[data-date], which the migrated <a class="heatmap-cell">
markup never sets, so it had been dead since the Svelte migration.

Rebuild dist/repo-intel.
…ions

Finishes the imperative→Svelte migration for everything except the hand-drawn
canvas timeline:

- The four "Overall" charts and the hour/dow pattern cards move from the
  getElementById + innerHTML rendering in charts.ts into OverallCharts.svelte
  and PatternCard.svelte. Each owns its canvas via bind:this and destroys its
  chart on unmount (the imperative version never tore them down). The reset
  affordance is now declarative (class:has-hidden) instead of a DOM-built button.
- initScrollRows / initSidebar become the dragScroll / scrollSpy Svelte actions
  in actions.ts — scoped per-element with proper teardown.

With those gone, the imperative "dashboard engine" collapses: dashboard.ts and
charts.ts are deleted and App.svelte calls buildTimeline directly in onMount.

Also drops the dead prop-mutation that wrote net/lc/avgPerDay onto the data
object: Table.svelte already derives those locally and the pie/ratio charts
derive them inline, so the mutation, the optional computed fields in types.ts,
and the one-line table.ts helper are removed.
Add Prettier as repo-root dev tooling so the editor and CI agree on style
instead of drifting:

- Root package.json (prettier + prettier-plugin-svelte) with format /
  format:check scripts, a .prettierrc (printWidth 100, matching the bulk of the
  frontend), and a .prettierignore for deps, build output, and generated data.
- `make format` / `make format-check`; CI runs format-check before the
  type-check so style drift fails fast.
- One-time reformat of the existing tree to the new config. CSS, markdown, and a
  few components shift the most since they weren't previously Prettier-clean.
- Drop web/.gitignore (the root .gitignore already covers web/node_modules and
  web/dist) and ignore the root /node_modules used by the tooling.

dist/repo-intel is rebuilt here because web/index.html (a build input) was
reformatted; the inlined JS/CSS bundles are byte-identical.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/lib/timeline.ts (1)

922-927: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use safe window.open flags for external navigation.

Both click handlers open external pages with "_blank" only. Add noopener,noreferrer to prevent opener access.

Suggested fix
-    if (hit) window.open(`${D.githubBaseUrl}/commit/${hit.c.h}`, "_blank");
+    if (hit) window.open(`${D.githubBaseUrl}/commit/${hit.c.h}`, "_blank", "noopener,noreferrer");
@@
-      if (url) window.open(url, "_blank");
+      if (url) window.open(url, "_blank", "noopener,noreferrer");

Also applies to: 1179-1186

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/timeline.ts` around lines 922 - 927, Replace plain window.open
calls that use only "_blank" with a secure form that prevents the new page from
accessing window.opener by passing noopener,noreferrer as the third argument;
specifically update the window.open(...) in the canvas click handler (the one
that uses findHit and opens `${D.githubBaseUrl}/commit/${hit.c.h}`) and the
other similar window.open call later in the file so they call window.open(url,
"_blank", "noopener,noreferrer").
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/App.svelte`:
- Around line 53-57: buildTimeline currently wires window/DOM listeners and
requestAnimationFrame loops but returns void, causing leaks on remount; change
buildTimeline to return a disposer function that cancels any pending rAFs and
removes all added event listeners (including window.resize and any DOM
listeners) and any animation callbacks, and ensure any resources from
createAuthorPopover/createCommitPopover that need cleanup are exposed/closed
inside that disposer or via their own dispose methods; then in onMount capture
the disposer (e.g., const teardown = buildTimeline(data, authorPopover,
createTimelineTooltip())) and return teardown from onMount so Svelte invokes it
on component destroy/remount.

In `@web/src/lib/actions.ts`:
- Around line 95-104: The onMouseDown handler starts a drag for any mouse
button; update onMouseDown to ignore non-primary buttons by returning early when
e.button !== 0 (i.e., only proceed for the primary/left button) before calling
stopInertia() and setting drag state (references: onMouseDown, stopInertia,
isDown, row.classList.add("dragging"), startX, scrollLeft, lastX, lastTime,
velocity).

In `@web/src/lib/components/Heatmap.svelte`:
- Around line 202-205: The anchor elements in Heatmap.svelte currently use
target="_blank" without rel and sometimes render href="#" when githubBaseUrl is
missing (see the anchor using cell.href and the earlier cell rendering logic),
which is insecure and poor UX; fix by only rendering an <a> when cell.href is a
non-empty, non-placeholder URL (or otherwise render a non-clickable element like
a <div>/<span>) and when keeping target="_blank" add rel="noopener noreferrer"
to the anchor, ensuring the logic that builds cell.href (the code referencing
githubBaseUrl and cell.href generation) does not fall back to "#" but yields
null/undefined so the conditional rendering can skip the link.

In `@web/src/lib/components/OverallCharts.svelte`:
- Around line 44-57: resetTimeline and resetPie call methods on timelineChart
and pieChart without ensuring the chart instances exist, which can throw if
invoked before onMount; guard each handler by checking the chart variable is
truthy before accessing its properties (e.g., return early if !timelineChart or
!pieChart), and apply the same existence checks to the other handlers referenced
around lines 211-216 so every chart access (getDatasetMeta, data,
getDataVisibility, toggleDataVisibility, update) is only executed when the
corresponding chart instance is initialized.

In `@web/src/lib/components/YearToggles.svelte`:
- Around line 27-33: The component currently conditionally renders the entire
toggle block based on years.length, which hides the "Current" button when years
is empty; change the rendering so the "Current" button (using the select
function and active variable) is always output and only the year-specific
buttons are wrapped in the conditional that checks years.length (i.e., keep the
<button ... onclick={() => select("current")}>Current</button> outside the {`#if`
years.length} block and move the years.map / {`#each` years as year} block inside
the conditional).

In `@web/vite.config.ts`:
- Line 32: The helper abs currently returns new URL(p, import.meta.url).pathname
which yields non-native Windows paths; update abs to use Node's fileURLToPath to
convert the URL to a proper filesystem path (import fileURLToPath from 'url' and
call fileURLToPath(new URL(p, import.meta.url))) so Vite resolve.alias paths
($components, $lib, $types) are correct on Windows; keep the function name abs
and only change the URL-to-path conversion.

---

Outside diff comments:
In `@web/src/lib/timeline.ts`:
- Around line 922-927: Replace plain window.open calls that use only "_blank"
with a secure form that prevents the new page from accessing window.opener by
passing noopener,noreferrer as the third argument; specifically update the
window.open(...) in the canvas click handler (the one that uses findHit and
opens `${D.githubBaseUrl}/commit/${hit.c.h}`) and the other similar window.open
call later in the file so they call window.open(url, "_blank",
"noopener,noreferrer").
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fca6e14e-93c0-40bc-8050-775de345ea2d

📥 Commits

Reviewing files that changed from the base of the PR and between 101e713 and 3dc1c6b.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • dist/repo-intel is excluded by !**/dist/**
📒 Files selected for processing (32)
  • .github/workflows/ci.yml
  • .gitignore
  • .prettierignore
  • .prettierrc
  • Makefile
  • README.md
  • package.json
  • web/index.html
  • web/src/App.svelte
  • web/src/data.ts
  • web/src/lib/actions.ts
  • web/src/lib/components/AuthorPopover.svelte
  • web/src/lib/components/CommitPopover.svelte
  • web/src/lib/components/ContributorCard.svelte
  • web/src/lib/components/Header.svelte
  • web/src/lib/components/Heatmap.svelte
  • web/src/lib/components/LangBar.svelte
  • web/src/lib/components/OverallCharts.svelte
  • web/src/lib/components/PatternCard.svelte
  • web/src/lib/components/Table.svelte
  • web/src/lib/components/TechGrid.svelte
  • web/src/lib/components/TimelineTooltip.svelte
  • web/src/lib/components/YearToggles.svelte
  • web/src/lib/format.ts
  • web/src/lib/popover-store.svelte.ts
  • web/src/lib/popovers.ts
  • web/src/lib/theme.ts
  • web/src/lib/timeline.ts
  • web/src/styles/app.css
  • web/src/types.ts
  • web/tsconfig.json
  • web/vite.config.ts
✅ Files skipped from review due to trivial changes (4)
  • .prettierignore
  • .prettierrc
  • web/src/types.ts
  • web/tsconfig.json
📜 Review details
🔇 Additional comments (25)
web/src/lib/popover-store.svelte.ts (2)

9-68: LGTM!


74-128: LGTM!

web/src/lib/popovers.ts (2)

24-82: LGTM!


88-100: LGTM!

.github/workflows/ci.yml (1)

24-31: LGTM!

.gitignore (1)

9-10: LGTM!

package.json (1)

1-13: LGTM!

web/index.html (1)

9-11: LGTM!

web/src/data.ts (1)

1-1: LGTM!

Also applies to: 10-10

Makefile (1)

12-20: LGTM!

Also applies to: 34-34

README.md (1)

50-50: LGTM!

Also applies to: 209-245

web/src/lib/components/ContributorCard.svelte (1)

23-51: LGTM!

Also applies to: 54-63, 65-105

web/src/lib/components/LangBar.svelte (1)

15-35: LGTM!

Also applies to: 37-85

web/src/lib/components/Table.svelte (1)

12-145: LGTM!

web/src/lib/components/AuthorPopover.svelte (1)

20-249: LGTM!

web/src/lib/components/CommitPopover.svelte (1)

11-155: LGTM!

web/src/lib/components/TimelineTooltip.svelte (1)

13-266: LGTM!

web/src/lib/components/PatternCard.svelte (1)

13-95: LGTM!

web/src/styles/app.css (1)

1-566: LGTM!

web/src/lib/theme.ts (1)

21-28: LGTM!

web/src/lib/format.ts (1)

2-2: LGTM!

Also applies to: 31-36, 72-73

web/src/lib/actions.ts (1)

1-94: LGTM!

Also applies to: 105-231

web/src/App.svelte (1)

3-52: LGTM!

Also applies to: 60-160

web/src/lib/components/Header.svelte (1)

1-43: LGTM!

web/src/lib/components/TechGrid.svelte (1)

1-111: LGTM!

Comment thread web/src/App.svelte
Comment thread web/src/lib/actions.ts
Comment thread web/src/lib/components/Heatmap.svelte
Comment thread web/src/lib/components/OverallCharts.svelte
Comment thread web/src/lib/components/YearToggles.svelte
Comment thread web/vite.config.ts Outdated
…ws paths

- dragScroll: ignore non-primary mouse buttons
- Heatmap: drop href="#" fallback; gate href/target/rel so links carry
  rel=noopener noreferrer and non-linkable cells stay inert
- timeline: add noopener,noreferrer to both window.open calls
- vite.config: make abs() Windows-safe and percent-decoded without
  pulling in @types/node (keeps it out of svelte-check scope)

Rebuilds dist/repo-intel.
@tyom tyom merged commit 6594e5a into main May 22, 2026
2 checks passed
@tyom tyom deleted the migrate-frontend-svelte-vite branch May 22, 2026 15:43
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