Commit Graph
66 Commits
Author SHA1 Message Date
github-actions[bot] 083be8d01c chore(demo): regenerate gallery 2026-04-19 03:47:40 +00:00
tiennm99andGitHub 3dd5e85806 fix(card): show 7 *named* languages + Other, not 6 + Other (#19)
With topN=7 the previous collapseOther kept only the first 6 entries
and added "Other" as the 7th row. A user expecting to see 7 actual
languages in the legend saw six named languages plus "Other" — the
exact complaint just raised about the profile repo's donut.

Flip the semantic: the "top N" slots are reserved for real languages,
and "Other" is an extra row when (and only when) there's a non-zero
tail past the Nth entry. Topologically that means up to 8 legend
rows — still fits the card frame (row 8 text baseline at y=195, card
height 200).

- TestDonutTopSevenPlusOther pins the new contract with a 9-language
  input.
- adversarialProfile in TestCardsFitFrame bumped to 9 languages so
  the stress test exercises the 8-row legend geometry.
- design-guidelines: the donut row re-reads "Up to 7 named languages,
  plus an 'Other' row when the tail is non-zero (8 rows max)".
v1.2.1
2026-04-19 10:46:10 +07:00
tiennm99andGitHub 059c8b11ad docs: focus each file on users or coworkers, drop unrelated content (#18)
project-roadmap.md went from a 146-line phase-by-phase history to a
48-line focused view: what's planned, what's out of scope. Completed
work is already in git log + GitHub Releases — the doc re-telling it
was the thing most likely to rot and least likely to be read.

project-overview-pdr.md: "Open questions" section dropped its stale
bullet list and now just points at project-roadmap.md (single source of
truth for planned work).

code-standards.md: drop the ".claude/ directory" commit rule — that's
a per-user workflow detail, not a project-level standard. Docs are for
users of the CLI/Action and coworkers of this repo, nothing else.
2026-04-19 10:34:02 +07:00
tiennm99andGitHub 65f17af2bc docs: collapse marketplace-publish step — already published (#17)
The repo is already listed on the Marketplace as ghstats-cards, so the
multi-paragraph "open the release page, tick the checkbox, re-publish"
instruction was stale. Replace with a one-line note pointing at the
existing listing and stating that new releases inherit visibility
automatically.
2026-04-19 10:26:57 +07:00
tiennm99andGitHub dab7fe0371 docs(readme): embed live dracula preview at the top (#16)
The README previously only LINKED to the demo gallery. Pull the dracula
theme inline so a reader can scan every card type + the LY/AT pairs
without leaving the page, and use the demo gallery for the other 64
themes. SVGs resolve against demo/dracula/ which the demo workflow keeps
fresh on every push to main.
v1.2.0
2026-04-19 10:24:34 +07:00
tiennm99andGitHub 88397e80a3 docs: resync after card fixes — title auto-fit, truncate, abbreviated ticks (#15)
Several recent code changes hadn't propagated to the docs:

- design-guidelines
  * Card frame title row: document the 11–15 px auto-shrink (not a flat
    15 px anymore).
  * Donut Top-N: already 7 (updated earlier).
  * Bar-chart section renamed to cover weekday + by-year too; document
    the peak-vs-dim highlight convention and the niceTicks yMax ≥ max
    invariant.
  * Add Heatmap / Stat-column (streak) / List (top-starred) card
    sections — they were missing entirely.
  * Rewrite "Text overflow" from "we don't truncate" to the current
    truth (truncate helper, formatTick abbreviations).
  * Replace dangling `truncateName` reference with `truncate`.

- code-standards
  * SVG output standards: call out truncate, formatTick abbreviation,
    header auto-fit so the card-review gate reflects what the renderers
    actually do.

- codebase-summary
  * Layout tree: svg.go / axis.go comments list the helpers they now
    contain; productive.go notes the weekday histogram.
  * demo/ tree shows the index vs per-theme split.
  * Data-flow diagram includes Weekday in the productive pass.
  * Test coverage row lists TestCardsFitFrame / TestFitTitleFontSize /
    TestNiceTicksCoversMax — the new invariant guards.

- system-architecture
  * Shared primitives list adds renderWeekday and renderHeatmap; donut
    blurb updated to "top 7".
  * New "Chart-geometry invariants" block documents niceTicks ceiling,
    formatTick abbreviation, header auto-fit.

- project-roadmap
  * Phase 7.5 bullet updated to describe the index + per-theme demo
    split (was single-README TOC).
2026-04-19 10:22:03 +07:00
github-actions[bot] fc29729aa2 chore(demo): regenerate gallery 2026-04-19 03:15:00 +00:00
tiennm99andGitHub d1034e6883 feat(card): show top 7 languages on donut cards instead of top 5 (#14)
Legend at x=20-155 fits 7 rows (y=55 to y=175); donut at cx=250, cy=110
is unaffected. Update the design-guidelines table to match. Stress test
still passes — the legend column never leaves its left-side gutter.
2026-04-19 10:13:07 +07:00
github-actions[bot] 72fcba7963 chore(demo): regenerate gallery 2026-04-19 03:10:37 +00:00
tiennm99andGitHub 43371a386a refactor(demo): split single 975-image README into index + per-theme pages (#13)
Loading demo/README.md was unusable — 65 themes × 15 SVGs = 975 images
in one page, and reloading made the tab lag for 10+ seconds. Restructure:

- demo/README.md shrinks to an index: one bullet per theme linking to
  that theme's page. Zero images.
- demo/<theme>/README.md (new) embeds the 15 SVGs for that theme only,
  grouped so the last-year / all-time variants sit side-by-side in HTML
  tables. Readers compare LY vs AT without scrolling back and forth,
  and fetch ~15 images instead of 975.

Per-theme page layout:

  ## At-a-glance       profile · stats · streak  (each full-width)
  ## Repos             top-starred · repos-per-language
  ## Contributions     heatmap · by-year, then
                       monthly shape: LY | AT  (paired)
  ## When you commit   hour-of-day: LY | AT
                       day-of-week: LY | AT
  ## What you commit   language share: LY | AT

pair() and single() bash helpers skip missing SVGs so a future
partial-render doesn't emit dangling <img> tags.
2026-04-19 10:08:44 +07:00
github-actions[bot] 9371cbaa92 chore(demo): regenerate gallery 2026-04-19 03:05:36 +00:00
tiennm99andGitHub 462d2af4dd refactor(card): audit title-fit logic — O(1) form + lock-in unit test (#12)
The title fitter was correct but the loop-down-from-maxFont form hid
its intent. Switch to the direct expression:

    ideal = floor(budget / (chars * 0.6))
    clamp to [11, 15]

Same answer for every title; easier to verify at a glance. Hoist the
four constants (leftInset=20, rightSafety=4, minFont=11, maxFont=15,
charRatio=0.6) to package scope so the new unit test can reference
them without re-declaring.

Utilization audit for realistic dracula titles (width=340, budget=316):

  Stats (5)                                        → 15 px (14 %)
  Top Starred Repos (17)                           → 15 px (48 %)
  Most Commit Language (all time) (31)             → 15 px (91 %)
  Commits by Hour (last year, UTC+7.00) (37)       → 14 px (98 %)
  Commits by Weekday (last year, UTC+7.00) (40)    → 13 px (99 %)
  Commits by Weekday (last year, UTC+12.75) (41)   → 12 px (93 %)
  200-char pathological                            → 11 px (floor)

TestFitTitleFontSize pins this table so a future charRatio tweak can't
silently regress any real title. TestCardsFitFrame (the end-to-end
check) plus this unit test now cover both the geometry and the picked-
font-size paths.
2026-04-19 10:04:02 +07:00
github-actions[bot] 31cf6e265f chore(demo): regenerate gallery 2026-04-19 02:55:52 +00:00
tiennm99andGitHub a3219e2892 fix(card): y-axis last tick must be ≥ data max (chartH overflow) (#11)
When niceTicks picks a step that doesn't divide the data max, the last
returned tick was the highest step multiple ≤ max. Callers used it as
yMax, so any data point > lastTick rendered a bar > chartH that poked
above the chart top into the title area.

Concrete case from the dracula demo: max=625 with step=100 → ticks
[0, 100, 200, 300, 400, 500, 600], yMax=600, bar height for 625 =
110*(625/600) = 114.58 — 4.58 px past the chart top and right against
the card title.

Fix in niceTicks itself: round the top tick UP to the next step multiple
(`last = ceil(max/step) * step`), so 625 yields [..., 600, 700] and the
same 625 bar lands at 110*(625/700) ≈ 98.2 px, with a clean 12 px gap
to the title.

This is the stable answer to title-vs-bar collision: regardless of
which weekday (or year, or month, or hour) holds the peak, the chart
headroom is built into the axis instead of leaned on per-card. The
title auto-shrink from the previous fix still applies — that's for
literal text width, an orthogonal problem.

Add TestNiceTicksCoversMax covering the cases (625, 99, 101, 7, 49,
999, 1001) that would have silently regressed before.
2026-04-19 09:54:28 +07:00
github-actions[bot] 891fd4a7cb chore(demo): regenerate gallery 2026-04-19 02:46:02 +00:00
tiennm99andGitHub 9dcb4505fa fix(card): auto-shrink card titles that don't fit at 15 px (#10)
productive-weekday's "Commits by Weekday (last year, UTC+7.00)" is 40
chars — at font-size 15 that renders ≈360 px wide, past the 340 px
frame. Before the previous stress-test run missed it because the
fixture left UTCOffsetLabel blank, so the title was the short
"Commits by Weekday (last year)" form that happens to fit.

Fix at the render layer instead of rewriting every long title:
header() now picks the largest font between 11 and 15 px at which the
title still fits in 316 px (card width − 20 left inset − 4 right
safety). 40-char titles land at 13 px; ≤ 35-char titles still render at
15 px as before. Readability floor is 11 px — far from the point at
which text becomes illegible, but a hard floor nevertheless.

Stress-test fixture now populates UTCOffsetLabel with "UTC+12.75"
(half-hour / quarter-hour zones make the widest title) so this class
of bug surfaces automatically from here on.
2026-04-19 09:44:12 +07:00
github-actions[bot] 927926751a chore(demo): regenerate gallery 2026-04-19 02:36:15 +00:00
tiennm99andGitHub 55421ebe3d fix(card): adapt to profile magnitude — abbreviate ticks, truncate long text (#9)
The stress test caught real overflows from profiles the author doesn't
have:

- Y-axis tick labels were raw integers. A user with 10,000+ yearly
  contributions or 1000+ monthly commits would render "10000" / "1000000"
  at text-anchor="end" against a ~28 px gutter — the digits spilled
  leftward past x=0. formatTick now abbreviates: 999→"999", 1500→"1.5k",
  12345→"12k", 1234567→"1.2M". No label exceeds 4 chars, all fit the
  gutter for every card that uses niceTicks.

- Profile details dumped Company / Location / Website / title verbatim,
  which works for the author (VNG, Ho Chi Minh, miti99.com) but not for
  40-char strings. Each row truncates at 40 runes; the title truncates at
  34. Uses a rune-aware truncate() helper hoisted out of top-starred-repos
  into svg.go so every list-style card can share it.

- Streak date range collapses to a single-year form ("Jan 2 — Dec 31")
  when start.Year() == end.Year() and to "YYYY — YYYY" across years. The
  previous "Jan 2 — Dec 31, 2025" format at 10 px × 21 chars pushed past
  the ~113 px column width.

The TestCardsFitFrame stress test was reading text-anchor and font-size
with a non-greedy regex that missed attributes whose position varied. It
now parses the opening <text> tag as a block and extracts each attribute
with its own regex, so text-anchor="end" / "middle" elements are no
longer false negatives. The check also estimates rendered width
(0.6 × font-size × len) and asserts the implied left/right edges stay in
the frame — catching exactly the class of bug the axis-tick case
represents.
2026-04-19 09:34:38 +07:00
github-actions[bot] 64f1dd5459 chore(demo): regenerate gallery 2026-04-19 02:28:58 +00:00
tiennm99andGitHub 93880f7ddd fix(card): eliminate frame overflows and add release-gate standard (#8)
Three cards overflowed the 340×200 frame for realistic profile data:

- contributions-heatmap: the classic case — 53 weeks at 9px cellSize + 2px
  gap pushed the grid out to x≈611. Shrink to cellSize=5, cellGap=1 so
  leftPad(22) + 53*6 = 340 (exact fit). Drop month labels within 20 px of
  the right edge so "Dec"/"Apr" can't stick past the frame.
- streak: the third column rendered "N / M" at font-size 28, centered at
  x=282. For 4+ digit totals (e.g. 584 / 3031) the text extended to x≈347.
  Refactor to show the active-days integer by itself in the big slot and
  push "of N total (P%)" into the small detail line that the other two
  columns already use.
- top-starred-repos: the per-row star icon sat at x=306 while the right-
  anchored number ended at x=334, so 5+ digit star counts collided with
  the icon. Drop the icon (card title already says "Top Starred Repos"),
  emit the count as "N ★", right-anchor at x=334 with a 6 px safety gap.

Add a new TestCardsFitFrame stress test that renders every card against an
adversarial profile (10-digit counts, 40-char names, 20 active years,
53-week span) and asserts every positional attribute stays inside the
frame. This is the automated half of the new "fit-the-frame invariant"
added to docs/design-guidelines.md + a pre-release review checklist in
docs/code-standards.md.

Bug reports will still surface text-overflow cases that the coordinate
check can't see (a text-anchor="middle" element has a single x attribute
but renders outward), so the docs also spell out the human-review step:
render dracula against tiny/typical/adversarial fixtures before release.
2026-04-19 09:27:17 +07:00
tiennm99andGitHub 4609688fe2 docs: correct card count 14 → 15 (weekday is LY + AT pair) (#7) 2026-04-19 09:15:50 +07:00
tiennm99andGitHub 1c7e99630e fix(demo): include all 15 cards in demo/README.md gallery (#6)
The hardcoded cards[] array in demo.yml still listed the original 9,
so the S-tier additions (heatmap, streak, by-year, weekday pair,
top-starred) were rendered into demo/<theme>/ but never referenced in
demo/README.md. Expand the array to the full 15 in README card-table
order.
2026-04-19 09:14:37 +07:00
tiennm99andGitHub 1c83d0d802 docs: resync remaining stale references after S-tier + demo changes (#5)
- deployment-guide.md: embed example now lists all 14 cards (was 9).
- codebase-summary.md: FetchProductive row includes Weekday / WeekdayAllTime;
  shared helpers list adds renderWeekday, renderHeatmap, mixHex/parseHex.
- system-architecture.md: per-commit fetch diagram shows the weekday bucket
  alongside the hour bucket.
2026-04-19 09:09:25 +07:00
github-actions[bot] 3e702556ac chore(demo): regenerate gallery 2026-04-19 02:05:38 +00:00
tiennm99andGitHub 9c21144ac4 chore: drop tracked output/ sample in favor of CI-built demo gallery (#4)
The dracula sample under output/dracula/ was a reference render for the
README. Now that demo/ auto-generates every card × every theme on each
push, the sample is redundant — and keeping it trackers means every
behavioral tweak shows up as a diff in committed SVGs.

- Delete output/dracula/*.svg.
- .gitignore: collapse `output/*` + `!output/dracula/` to a plain `output/`.
- demo.yml: drop `output/**` from paths-ignore (no longer part of any push).
- README / docs: point readers at demo/ instead of output/dracula/.
2026-04-19 09:03:44 +07:00
github-actions[bot] 4a4aa4aa10 chore(demo): regenerate gallery 2026-04-19 02:00:54 +00:00
tiennm99andGitHub fd4c70e53e feat(card): add S-tier cards — heatmap, streak, by-year, weekday, top-starred (#3)
Five new cards, all derived from data FetchProductive / FetchProfile already
pull, so zero additional API calls:

- contributions-heatmap: 7×53 calendar grid with a 5-bucket intensity ramp
  mixed from each theme's Background→Accent so palettes with no dedicated
  heat ramp still render sensibly.
- streak: current streak, longest streak with date ranges, active/total days.
- contributions-by-year: one bar per active year, peak year highlighted.
- productive-weekday + -all-time: 7-bar day-of-week mirror of the hour-of-day
  cards; FetchProductive now also fills Weekday / WeekdayAllTime histograms
  during the same commit-history pass.
- top-starred-repos: top 5 owned non-fork repos by stargazer count; threads
  Stars through RepoInfo.

Card count: 9 → 14. Registered in allCards grouped by recency (last-year
block, then all-time block). Render test extended to cover all new files
and realistic daily-series inputs.
2026-04-19 08:58:59 +07:00
tiennm99andGitHub 9dffc2234b docs: link demo gallery from readme and correct stale theme count (#2)
- README points to demo/ as an auto-generated theme preview.
- Bump "60+ / 61 themes" references to the actual 65.
- Record Phase 7.5 in roadmap and list demo.yml + demo/ in layout.
2026-04-19 08:45:40 +07:00
github-actions[bot] 04a9e60eff chore(demo): regenerate gallery 2026-04-19 01:35:49 +00:00
tiennm99andGitHub 6867beab9c ci(demo): add workflow rendering every card for every theme (#1)
Runs on push to main (skipping its own demo/output/markdown commits),
builds the ghstats binary, renders every theme for the repo owner,
and writes a demo/README.md gallery back to main.
2026-04-19 08:34:19 +07:00
tiennm99 f90a5baec5 docs(readme): add tiennm99/tiennm99 as 'In the wild' example 2026-04-19 00:12:59 +07:00
tiennm99 e5b6daae5a docs: resync with current state across all project docs
- design-guidelines: every dimension was stale — card frame 340x200
  (was 500x220), corner radius 6, title 15px at (20,30), row y0/dy,
  donut centre (250,110) r=55/30, topN=5, legend y0=55 dy=20, bar chart
  area [35,325]x[45,155], area chart [28,312]x[45,150], icon scale 0.75.
- code-standards: FetchContributionsAllTime signature now ctx-first,
  viewbox 500x220 → 340x200.
- codebase-summary: test coverage lists main_test.go +
  TestDonutSingleSlice/Empty; filename convention says plain kebab-case
  (no numeric prefix).
- project-overview-pdr: forks/private defaults now on, not off.
- project-roadmap: add Phase 7 (Marketplace polish — resize, numeric-
  prefix drop, v1 floating tag, rename-rollback). Renumber planned
  phases 8-11. Fix "hard width 500 px" limitation.
- deployment-guide: document update-major-tag job; note
  Marketplace listing name is `ghstats-cards`.
v1.1.2
2026-04-19 00:09:00 +07:00
tiennm99 d7659129a5 ci(release): auto-move floating major tag after successful release
Adds an update-major-tag job that runs after test/docker/binaries all
pass. Extracts the major component from the pushed tag (v1.2.3 → v1)
and force-moves that tag to the current commit, then pushes.

Consumers using `uses: tiennm99/ghstats@v1` now pick up each new
minor/patch release automatically without a workflow edit on their
end. The v1 tag only moves if every artifact job succeeded, so it
never points at a commit whose binaries or Docker image failed to
publish.
2026-04-18 23:58:15 +07:00
tiennm99 729f1a0611 Revert "chore: rename module + references to tiennm99/ghstats-cards"
This reverts commit accc4b6. Repo stays as tiennm99/ghstats; the
Marketplace display name ("ghstats-cards" in action.yml) is the only
place the new name remains, since that field requires uniqueness on
the Marketplace.

plans/reports/* were added alongside the rename in the same commit
and are preserved by not deleting them in this revert.
2026-04-18 23:51:33 +07:00
tiennm99 accc4b62b2 chore: rename module + references to tiennm99/ghstats-cards
Matches the Marketplace name; repo is being renamed in lockstep.

- go.mod module path: github.com/tiennm99/ghstats →
  github.com/tiennm99/ghstats-cards
- Import paths across every .go file updated.
- README badges, install snippets, and the 'go install' line point
  to the new URL/path.
- docs/deployment-guide.md workflow template, Docker image path, and
  release edit URL updated.

Breaking for consumers pinned to the old URL; they need to swap
tiennm99/ghstats → tiennm99/ghstats-cards in workflows and switch
Docker pulls to ghcr.io/tiennm99/ghstats-cards. GitHub's HTTP
redirect covers git clones but GHCR does NOT redirect — users must
update image URIs manually.
2026-04-18 23:40:29 +07:00
tiennm99 289d00c227 docs(readme): link back to Marketplace listing + add badges
- Marketplace / Release / License badges at the top.
- Explicit Marketplace + source links under the intro so visitors
  arriving from the Marketplace page can jump to the repo and vice
  versa.

Also set repo topics via `gh repo edit`: ghstats, ghstats-cards,
github-action, github-marketplace, profile-readme, profile-stats,
stats-cards, svg-cards, go, github-stats.
2026-04-18 23:36:28 +07:00
tiennm99 52864bdd18 chore(action): rename Marketplace display to ghstats-cards
"ghstats" is already taken by an existing action/user/org on the
GitHub Marketplace, blocking publication. Rename the action's Marketplace
display name to ghstats-cards (still more descriptive of what it
produces). The repo URL, Docker image tag, and import path stay
tiennm99/ghstats — only the Marketplace listing label changes.
v1.1.1
2026-04-18 23:30:21 +07:00
tiennm99 2fa661f9f5 build: refresh dracula samples at 340x200 v1.1.0 2026-04-18 23:26:55 +07:00
tiennm99 ce70dcf87c refactor(card): shrink cards from 500x220 to 340x200
Two ghstats cards side-by-side at 500px overflow GitHub's README column
width (~816px). Matching github-profile-summary-cards' 340x200 lets
consumers place two cards per row cleanly.

- Shared header() rect corner radius 8→6, title font 18→15, title anchor
  (25,35)→(20,30).
- All card layouts reworked:
  * profile + stats: row padding 70→55 / 24→20, icon 14→12, value
    font 13→12, stats right-anchor x=475→320.
  * donut_chart: centre (380,120)→(250,110), outerR 70→55, innerR 38→30,
    legend y0 70→55, swatch 12→10, topN 6→5.
  * productive-time: leftAxis 50→35, topPad 60→45, barGap 2→1.
  * contributions: leftPad/rightPad 35→28, topPad 60→45, chartH 120→105.

Visual parity with github-profile-summary-cards; two-per-row layouts in
profile READMEs now fit.

Also: document the one-time GitHub Marketplace publishing step in
deployment-guide (no CLI flag exists — UI checkbox only).
2026-04-18 23:26:48 +07:00
tiennm99 8cc31d3d97 docs: refresh for context plumbing, rate-limit handling, phase 6
- Fetcher signatures across codebase-summary and system-architecture
  now show the ctx-first arguments and document the rate-limit retry
  loop in Client.query.
- Attribution pseudo-code hoists the per-repo total out of the commit
  loop to match the current implementation (I6).
- Failure-modes table enumerates primary rate-limit retry, per-year
  nil-user warn, and -timeout / Ctrl-C cancellation.
- design-guidelines notes the single-slice donut special case.
- deployment-guide's release section documents the new test gate and
  the SHA-pinned Docker/GHA actions; troubleshooting adds the
  rate-limit-reset-too-long error. Rate-limit section describes the
  sleep-and-retry policy and -timeout flag.
- project-roadmap records Phase 6 (code-review remediation) as done,
  renumbers later planned phases, links the new review report.
v1.0.0
2026-04-18 22:55:32 +07:00
tiennm99 97b0923809 build: update stats sample to match N9 label change 2026-04-18 22:44:31 +07:00
tiennm99 992846855d test: meaningful escape check, single-slice donut, utcOffsetLabel
- I4 — TestRenderAll now seeds Name/Company with XML-significant chars
  that actually hit the render pipeline via cardTitle(). Previous test
  checked Bio which is no longer rendered, so the assertion was vacuous.
- New TestDonutSingleSlice guards against the I1 empty-arc regression:
  asserts the single-slice path emits <circle> primitives and not the
  degenerate A-command path.
- New TestDonutEmpty covers the zero-stats fallback.
- New TestUTCOffsetLabel pins the UTC±N.NN format across UTC,
  Asia/Saigon, Asia/Kolkata (half-hour), Asia/Kathmandu (quarter-hour).
  Catches %+.2f regressions.
2026-04-18 22:44:25 +07:00
tiennm99 27c8014270 feat(client): thread context.Context and honor rate-limit backoff
- N2 — Client.query, FetchProfile, FetchContributionsAllTime, and
  FetchProductive now take context.Context. main.go builds a root
  context with a -timeout flag (default 30m) and cancels on
  SIGINT/SIGTERM so Ctrl-C aborts in-flight pagination cleanly.
- N6 — on HTTP 429 or 403 with X-RateLimit-Remaining: 0, the client
  now parses Retry-After / X-RateLimit-Reset, sleeps (capped at 5
  minutes), and retries once. Longer reset windows surface as errors
  so the caller can reschedule the Action rather than burn 55 min of
  runner time idle.
2026-04-18 22:43:50 +07:00
tiennm99 262a8e1482 refactor(model): rename TotalContributions → TotalContributionsLastYear
N8 — the field is set from contributionsCollection.contributionCalendar,
a rolling last-year window, not a lifetime total. Rename + updated
comment makes the semantics explicit so future readers don't confuse
it with TotalCommitsAllTime.
2026-04-18 22:43:38 +07:00
tiennm99 6543bab6d8 fix: donut single-slice, partial-year warn, hoist per-repo total
- I1 — donut chart with a single slice (100%) now renders via two
  concentric <circle> elements instead of a degenerate SVG arc that
  drew nothing. Reproduced with a standalone probe; regression test
  added separately.
- I2 — FetchContributionsAllTime logs a warn to stderr when a year's
  query returns nil user data so callers notice partial results
  instead of rendering an empty all-time card silently.
- I6 — attributeCommit() receives the repo's byte total precomputed
  once per repo rather than re-summing language edges for every
  commit in the inner loop.
2026-04-18 22:43:24 +07:00
tiennm99 8a6a241160 chore: small hygiene fixes from code review
- I3 — update FetchOptions doc to describe zero-value vs CLI-flag defaults.
- I5 — release workflow gates docker/binaries on a test job; tags no
  longer ship broken artifacts.
- N1 — replace handwritten joinErrs with strings.Join.
- N3 — truncate() now backs up to a UTF-8 rune boundary so error
  messages never end on a split codepoint.
- N4 — pin Docker base images (golang:1.26-alpine, alpine:3.21) to
  SHA256 digests.
- N5 — pin third-party GitHub Actions to commit SHAs with version
  comments for readability.
- N9 — drop the "(non-fork)" qualifier from the stats card label; the
  underlying GraphQL doesn't actually filter forks, so the phrasing
  was misleading.
2026-04-18 22:43:14 +07:00
tiennm99 d3fc27f33f feat: flip include-forks / include-private defaults to true
Most users reach for ghstats to show their real activity. Making the
two visibility toggles opt-out rather than opt-in gives accurate stats
out of the box.

- -include-forks        false → true
- -include-private      false → true
- include_forks input   false → true
- include_private input false → true

Private-repo data silently no-ops when the token lacks repo scope, so
the default is safe for default github.token runs — it just won't
surface more than read:user already allows. Users who want the old
public-only behavior pass -include-private=false or set the Action
input to "false".
2026-04-18 22:20:46 +07:00
tiennm99 a02d27511a refactor(card): drop numeric prefix from output filenames
Files now land at output/<theme>/profile-details.svg etc., without
leading 0-8 prefixes. README authors embed cards by name, so the
lexicographic-sort rationale for the prefix no longer applies.

- All Filename() methods + the allCards ordering comment updated.
- Tests updated to expect the 9 unnumbered filenames.
- README, deployment-guide, codebase-summary, roadmap references
  refreshed.
- Dracula sample SVGs regenerated under new names.
2026-04-18 22:20:09 +07:00
tiennm99 8b3a8e1932 docs: add project documentation set
Seven canonical docs under docs/ per the project structure convention:

- project-overview-pdr.md   users, non-goals, requirements
- codebase-summary.md       directory layout, module responsibilities
- system-architecture.md    runtime phases, GraphQL flow, SVG primitives
- code-standards.md         YAGNI/KISS/DRY, Go conventions, commit rules
- design-guidelines.md      frame dimensions, theme roles, per-card specs
- deployment-guide.md       Action/binary/Docker paths, release process
- project-roadmap.md        done phases (0-5), planned phases (6-9)

All files under the 800-line cap. Each leans on tables; grammar
sacrificed for concision per project rules.
2026-04-18 22:10:09 +07:00
tiennm99 0168fd1f56 docs(readme): refresh for 9-card layout, seed sampling, visibility flags 2026-04-18 22:10:00 +07:00