LeetCode Tracker
Completed
Tool · DevTools

LeetCode Tracker

2025

Manifest V3 Chrome extension that watches leetcode.com for accepted submissions, tallies them per day in chrome.storage, and renders today/total counts plus a seven-day bar chart in the popup. The chart loads Chart.js from a CDN, which Manifest V3's default extension CSP is entitled to refuse — vendoring it is the outstanding fix.

Built with
JavaScriptJavaScript
Chrome ExtensionsChrome Extensions
Chart.jsChart.js
Project Details

STATUS
Completed
YEAR

2025

TYPE

Tool · DevTools

TAGS
Extension
Productivity

I wanted a daily count of problems solved without opening my LeetCode profile and squinting at the heatmap. An evening's worth of extension, and it has been running ever since.

The whole thing is seven files and under a hundred lines of JavaScript. It is here because the interesting part of a tool this small is the decision not to build the larger version of it, and because the larger version was in fact started and abandoned.

How it counts

There is no API call and no account linkage. A content script on leetcode.com runs a MutationObserver across document.body and watches for LeetCode's own .text-green-s element to appear containing the word "Accepted" after a submission. Each sighting increments a counter keyed by today's ISO date in chrome.storage.local.

The popup does the rest of the work: it sums every value in the history object for an all-time total, reads today's key for the daily figure, takes the last seven keys and renders them as a Chart.js bar chart with the y-axis pinned to integers, since half a solved problem is not a thing.

What's in it

  • Manifest V3 with storage, scripting and tabs permissions, and host access scoped to leetcode.com only.
  • A per-day history object rather than a flat counter, which is what makes the chart possible without any extra bookkeeping.
  • A seven-day rolling bar chart in a 300x200 popup canvas.
  • Today and all-time totals derived from the same store, so there is nothing to keep in sync.
  • A registered service worker that is an empty file. Everything happens in the content script and the popup; the background entry stays in the manifest for the day it needs to.
  • No options page, no sync storage, no export. The history object is local to the browser profile and stays there.

The storage model is the whole design

{ "2025-06-14": 3, "2025-06-15": 1 } and nothing else. One object, one write per accepted submission, read-modify-write from the content script. Every number the popup shows is derived from it at render time — today's count is a key lookup, the all-time total is a reduce, the chart is the last seven entries. There is no second counter to drift out of step with the first, no migration to write when the display changes, and adding a thirty-day view would be a one-character edit.

The one place that model leaks is ordering. The chart takes Object.keys(history).slice(-7), which relies on insertion order rather than sorting the dates. Since keys are only ever added on the day they represent, insertion order happens to be chronological, and it stays correct right up until you restore a backup or edit the store by hand.

The parts that would not survive contact with a second user

The DOM hook is as fragile as it sounds, and for a tool with one user that is the right trade. Watching a rendered class costs ten lines; anything sturdier means authentication and a GraphQL client for a number I glance at once a day. But the fragility is worth stating precisely rather than waving at.

.text-green-s is a utility class in LeetCode's own stylesheet, not a stable contract. A rebrand renames it and the extension silently stops counting, with no error and no visible difference other than a flat chart. The observer also has no debounce and no record of what it has already counted: it fires on every mutation batch, and if a batch arrives while the accepted banner is still on screen, the same submission increments the counter again. In practice the banner and the surrounding re-render settle fast enough that this rarely bites, but "rarely" is doing real work in that sentence.

The popup loads Chart.js from a jsDelivr URL, with a second <script src="chart.js"> tag next to it pointing at a local file that is not in the directory. Manifest V3's default extension content security policy does not permit remote script in extension pages, so the honest statement is that the chart depends on a script tag that the platform is entitled to refuse. Vendoring the library into the extension is the fix and takes about a minute.

The leaderboard that did not happen

The original idea was social: a shared board where a group of people could see each other's daily counts, with this extension as the collector. That repo exists, a Create React App client and a Django 4.2 server, and it stops at two commits. The client is still the default CRA splash page. The server is django-admin startproject output with no apps, one URL pattern pointing at /admin, and the auto-generated insecure secret key still in settings.py. The only original content in it is a rules.md laying out folder conventions for code nobody wrote.

That is a fair record of where the effort actually went. The extension solves the problem I had. The leaderboard solved a problem I thought I would have, and the moment it needed accounts, a sync protocol and somewhere to host it, the ratio of work to benefit stopped making sense.

Project Details

STATUS
Completed
YEAR

2025

TYPE

Tool · DevTools

TAGS
Extension
Productivity