ReactMarkdown was running with no plugins, so README headers full of
raw <div align=center>, tables, <details>/<summary>, and other GFM
markup rendered as escaped text. Wire in remark-gfm for tables and
GFM features, rehype-raw for embedded HTML, and rehype-sanitize with
an extended schema that permits README-typical tags and attributes
(align, target, width/height, picture/source, etc.) while still
blocking script/iframe/object — READMEs come from external repos so
they need adversarial-input handling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ingestion paths each had a gap that let non-default-branch work
slip through: /search/commits silently excludes forks, the per-repo
REST commit scan only walked the default branch, and the user events
feed ages out after 90 days. Catch them by enumerating branches per
repo and scanning each (with per-branch state cursors so a brand-new
branch isn't cut off by the default branch's cursor), pre-filtering
branches via a GraphQL HEAD-author check so big upstream forks like
azure-docs don't trigger hundreds of wasted REST calls, treating
GitHub's HTTP 500 on author-filtered empty branches as "no commits"
rather than a server error, and adding fork:true to the search query.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the standard HTML meta description (for SEO), og:locale, and
og:site_name tags flagged by Open Graph validators.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
usvg's default Options creates an empty fontdb, so no fonts are found
for text rendering regardless of what's installed. Load system fonts
into a fontdb::Database and set the default font family to Noto Sans.
Also picks up a formatting change to index.html from a linter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compute cell size from available width so the graph fills the canvas
instead of rendering at a fixed small size. Scale year labels
proportionally. Position headline and subtitle at the top with the
graph centered in the remaining space.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Resize OG image from ~676x216 to 1200x630 (recommended size)
- Add "rob thijssen" headline text overlay to the OG image
- Center the contribution graph within the canvas
- Expand og:title to 55 chars and og:description to 148 chars
to meet social platform optimal lengths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show days on the X axis and volume on the Y axis, replacing the
horizontal bar layout with vertical bars for a more natural
time-series reading direction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SVG text elements had no font-family, causing usvg to default to
Times New Roman which isn't installed on the server. Specifying
sans-serif uses the system default and silences the warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Increase from 10 to 14 rows so languages visible in the contribution
graph (e.g. Svelte, C++) also appear in the legend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace fixed green palette with per-period dominant language colors.
Each circle's hue reflects the language with the most commits for that
day (last-year graph) or month (all-time graph), with opacity scaled
by volume quartile. Language data comes from the existing language
daily counts endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When viewing a date range with zero public activities, the status line
now shows the count of private contributions (derived from daily counts
which include private repos). Helps explain gaps in the public timeline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aggregate graph endpoints (daily counts, language daily counts, source
summaries, OG image) now include private repository activity. These
endpoints only expose numeric counts — no commit messages, repo names,
or other metadata — so private details remain hidden. The activity
timeline continues to serve only public events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Transpose AllTimeGraph to show years on X axis and months on Y axis
instead of year-per-row with weekly columns. Add TopLanguages bar chart
(all-time code volume by language) and ContributionStats panel (current
and longest streaks, busiest day, active days, weekday averages) in a
three-column row matching the project card grid.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract LanguageBar into a shared component used by both DashPage
(compact, bar only) and ProjectPage (full, with percentage labels).
Remove redundant forge source text from project cards since the
forge icon already indicates it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace straight line segments with cubic bezier curves using
Catmull-Rom to bezier conversion (tension 0.5) for a smoother
stream graph visualization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each commit was counted once per language in the repo regardless of
that language's share, so Shell (present in many repos as small
deploy scripts) appeared larger than Rust. Now weights each commit
by the language's byte proportion in the repo (e.g. a commit to a
95% Rust / 5% Shell repo contributes 0.95 to Rust, 0.05 to Shell).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full-stack feature showing programming languages by commit activity
as a stream graph on the dashboard.
Backend:
- migration: repo_languages table (source, repo, language, bytes, color)
- worker: fetch language breakdowns via GitHub GraphQL (batched,
20 repos/request) and Gitea REST API during poll cycles
- API: GET /v1/languages/daily (daily commit counts per language),
GET /v1/languages/repos (all stored repo language data)
- fix timezone bug in daily_counts and language_daily_counts: the
PostgreSQL server timezone (Europe/Sofia, UTC+3) shifted day
boundaries, miscounting events near midnight. Now uses explicit
UTC boundaries in generate_series JOINs.
- use per-source CASE for repo name extraction in language query
to match gitea payload structure (repo.full_name vs repo.name)
- Gitea languages use GitHub colors via COALESCE fallback
Frontend:
- LanguageStreamGraph component: pure SVG stream graph, weekly
buckets, centered baseline, top 8 languages + Other, GitHub
canonical language colors, legend with color dots
- DashPage/ProjectPage: fetch repo languages once via new endpoint
instead of per-repo forge proxy calls (eliminates 200+ GitHub
API calls and 403 rate limit errors)
- removed fetchLanguages forge proxy wrapper (dead code)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The REST /user/repos endpoint only returns repos where the user is
owner, collaborator, or org member. Repos contributed to via PRs
(e.g. polkadot-js/api, zed-industries/zed) were never discovered
and their commits were missing from moments.
Now supplements /user/repos with a GraphQL
repositoriesContributedTo query, which returns all repos the user
has committed to, opened issues/PRs on, or reviewed — with cursor-
based pagination and no result cap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After initial backfill, scan_repo was fetching only page 1 (100 most
recent commits) per repo. If more than 100 commits landed between
7-day polls, older ones in that window were permanently missed.
Now stores the newest commit date in poller_state.last_modified and
passes it as &since= on subsequent polls, with full pagination, so
only genuinely new commits are fetched but none are skipped.
On first poll after deploy, last_modified is NULL so no since filter
is applied — triggering a full re-backfill that catches any
previously missed commits.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover all data sources (github events/search/repo, gitea with org
discovery, hg revset queries, bugzilla), frontend routes (dash with
contribution graphs, activity timeline with timespan filtering,
project detail with readme/languages, cv), api endpoints including
forge proxy and og image, environment variables, and deployment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Automated/bulk-commit repos score -1 so they sort last regardless
of recency or volume.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The commit presentation layer only checked repository.full_name,
missing commits ingested by github_repo which store the repo name
in _repo instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The events query's COALESCE for github source was missing _repo,
so per-repo commit events from github_repo had no repo match and
project pages showed 0 activities.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add SVG icons for each forge before the repo name on dashboard cards.
Icons sourced from user-provided SVGs in ui/public/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /repos/{owner}/{repo}/commits endpoint doesn't include repo info
in its response. Without _repo in the payload, these commits were
invisible to the projects query. Add _repo to parse_commit and include
it in the COALESCE chain for github source repo extraction.
After deploy, reset github-repo poller state to re-ingest with _repo:
DELETE FROM poller_state WHERE source LIKE 'github-repo%';
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add "in N repositories" to both the year and all-time graph summary
lines. Year graph counts repos with overlapping activity; all-time
graph uses total project count. OG image includes repo count too.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The static asset regex matched .png in /api/v1/og/contributions.png
before the /api/ proxy block, returning 404. Add negative lookahead
to skip /api/ paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add /v1/og/contributions.png endpoint that builds an SVG of the
all-time weekly contribution graph (one row per year) from daily
counts, then rasterizes to PNG via resvg. Served with 1h cache.
Add og:image and twitter:card meta tags to index.html pointing at
the endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure the all-time contribution graph from a single row of ~700
circles (sub-pixel when scaled) to one row per year with ~52 weekly
columns, matching the width of the daily graph above. Year labels on
the left.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add AllTimeGraph component showing one circle per week across the full
history (earliest event to today). Uses the /sources endpoint to find
the earliest date, then fetches daily counts and aggregates to weekly.
Clicking a week navigates to /activity/YYYY-MM-DD..YYYY-MM-DD.
Update parseTimespan to handle both date-only (YYYY-MM-DD) and full
ISO datetime strings in range expressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use viewBox + width=100% instead of fixed pixel dimensions so the
SVG scales to match the project card grid below.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add /v1/activity/daily endpoint returning per-day event counts via
generate_series + LEFT JOIN. Frontend renders an SVG contribution
graph with circles colored by quantile-based thresholds. Clicking a
day navigates to /activity/YYYY-MM-DD showing that day's events.
New /activity/:timespan route parses single dates (YYYY-MM-DD) and
ranges (YYYY-MM-DD..YYYY-MM-DD) from the URL to initialize the
activity timeline filter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
atob() produces Latin-1 strings, mangling multi-byte UTF-8 characters
like box-drawing glyphs. Use TextDecoder for correct UTF-8 handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add /v1/forge/{source}/* proxy endpoint to the API server with an
allowlisted set of hosts. Frontend readme and language requests now
go through the proxy instead of hitting forge APIs directly (Gitea
has no CORS headers). Gitea readme fetch tries README.md, readme.md,
and Readme.md casings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use CASE/source instead of COALESCE for repo name extraction — Gitea's
repo.name is the short name while full_name includes the owner prefix.
Fix Gitea README fetch to use /contents/README.md with base64 decoding
instead of the nonexistent /readme endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ProjectPage fetches README (raw markdown) and language breakdown from
GitHub/Gitea REST APIs, rendering the readme as markdown and languages
as a colored proportional bar with labels.
Dashboard cards lazily fetch top 3 languages per repo and display them
inline. Language color map covers common languages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>