More Projects
59 in totalLeetCode 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.
Project Details
2025
Tool · DevTools
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,scriptingandtabspermissions, and host access scoped toleetcode.comonly. - 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
2025
Tool · DevTools