feat: add a practice history to the dashboard
Codewars keeps no history outside the completed list on a profile, and the
dashboard is a suggested kata, two promotions and a forum feed. A second
gate, onDashboardPage(), now puts a card above the suggested kata: a
timeline grouped by day with the languages each kata was solved in and a
link back into the trainer, a bar of the last thirty days, and the current
streak.
The completions come from the public API rather than from any page, cached
in GM storage for ten minutes and painted from cache before the refresh
lands, so arriving on the dashboard does not redraw it. The username comes
from the header's profile link; there is no /me endpoint.
Two things this needed from existing code. removeAds() only marks nodes,
and the rule acting on the mark lives in the trainer sheet, so the
dashboard sheet repeats it. nearestAdContainer() climbs to
div[class*='w-full'], which on the dashboard is the whole main column: one
house ad hid the entire page, so isProtected() now refuses any node
containing the page's own content.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
feat: rework the history card around a year calendar
The first cut of the card was a thirty-day bar, a refresh button and rows
carrying only a name. Four changes, all of them things the card is read
for rather than things it does.
The bar becomes a GitHub-style calendar: one column per week over a year,
week-aligned so every column is a full Sunday-to-Saturday, month labels
along the top, and the days after today drawn as holes rather than as
zeroes so the current week does not read as a lapse.
Rows now carry the kata's rank and a Train button. Ranks are not in the
completions payload, so they come from the kata endpoint through a pool of
three and are cached for good; the list renders first and the badges fill
in. Rank colours and language chips reuse Codewars' own rank colours and
icon font, with a text fallback for languages the font has no glyph for.
The allies box and the forum feed are hidden outright instead of folded: a
fold is still a row to skip past. The refresh button is gone; the card
already refreshes itself on a TTL.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
feat: make the history calendar a full-width month
The year graph left most of the card empty and asked to be read at a
glance it could not support. It becomes one month, in the site's own
colours, filling the card.
The grid is transposed for that to be possible: weekdays are columns and
weeks are rows. A month is five weeks, and five square columns spanning
this width would be a grid thirteen hundred pixels tall; seven columns of
one fraction each fill the width at thirty pixels a row. Each cell now
carries its day, and the 1st carries its month, which is the only label a
single-month grid needs.
The ramp is Codewars' own: the legacy red through the orange to the honor
gold, taken from the site's custom properties rather than picked to match
them.
The card also moves below the suggested kata — what to do next is the
reason to open the dashboard, the history is what you look at afterwards.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
fix: shrink the calendar rows
At thirty pixels a row the month took as much of the card as the list it
sits above. Twenty is still tall enough for the day numbers.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
fix: drop the day numbers from the calendar
At sixteen pixels a row the grid is read as a shape, and thirty-five
numbers inside it are noise. The date moves to the cell's tooltip, and the
month the grid covers is stated once in the card header, where it does not
compete with the colour.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
feat: make the calendar a year-wide wall of squares
A month of wide cells filled the card but carried a month. A year of
squares carries twelve times as much and, at this width, is the same
height: fifty-three columns of one fraction each land the cells near
fifteen pixels.
Two things had to be right for that. The tracks are minmax(0, 1fr) — a
plain 1fr cannot shrink below its min-content, which a cell with an
aspect-ratio supplies, so the wall overflowed the card. And the weekday
labels now live in the same grid as the cells, filled column by column:
as a separate column they stretched to the grid's height, aspect-ratio
turned that height back into a width, and the cells came out half again
too wide for their tracks.
The ramp is one hue at four lightnesses — Codewars' red is hsl(10 71% 41%)
— rather than red through orange to gold, which could not be put in order
by eye.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
feat: use Codewars' own rank badge in the history
The rank was a bordered pill approximating the site's colours. It is now
the site's own hexagon markup, so the shape, the border and the colour
come from Codewars' stylesheet and follow it when they restyle. The
colour arrives in an API response, so it is matched against the six known
rank classes rather than interpolated into one.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
fix: size the trainer panes by measurement, and follow the light theme
Three findings from a pass over the interfaces, in both themes.
The panes were sized against a constant strip height. The strip is the
body padding plus the kata title block, and that block grows a line when
the AI panel narrows the column and its stats row wraps, so the constant
was six pixels short and the TEST row was clipped. A ResizeObserver
publishes the measured height as --cw-title-h and the calc() uses it.
The history card asked for --color-text, which Codewars does not define,
so it fell back to the value written for the dark theme and the card read
as light grey on white. The token is --color-ui-text. Both harnesses now
check every custom property the sheets read against the set the site
actually defines, because nothing about this is visible in the dark theme
— the check found a second name on its first run.
The calendar ramp now darkens on the light theme and lightens on the dark
one, so a busier day always moves away from the page rather than towards
it.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
feat: add per-language stats and stop the list scrolling inside the card
Two things the card was missing and one it should not have had.
The header carries a chip per language: how many kata it accounts for and
the rank held in it. Neither endpoint has both halves — the completions
have no rank and the user resource has no per-language count — so the
count is derived from the completions and the rank comes from the user
resource, fetched alongside and cached with it.
The list showed twenty-five rows through a three-hundred-pixel window, so
a card on a scrolling page had a scrollbar of its own competing for the
wheel. It now shows ten and links to Codewars' completed-solutions page
for the rest, which has the solutions too.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk
feat: keep a way back to a kata you did not finish
Codewars records that a kata was completed, never that one was opened.
Press TRAIN, get pulled away, and the kata is gone: the trainer suggests a
different one on every dashboard render, and nothing anywhere lists what
you had started.
Both halves of the answer already existed here. The trainer page knows
which kata is open, so it writes the key, the language, the name and the
rank; the dashboard already fetches the completions, so it subtracts them
and shows what is left with a Resume link. Matching is per language —
solving a kata in C says nothing about the Python attempt left open — and
against both id and slug, because the trainer URL carries one or the other.
This turned up a bug older than the feature: kataRank() took the first
.small-hex under #app, and buildHeaderMenu() moves the reader's own rank
badge into that subtree, so it returned the reader's rank rather than the
kata's — in the card, and in every context block the tutor was sent.
Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_01WcD1hYpBTvWD3oNZy23hzk