mirror of
https://github.com/tiennm99/ghstats.git
synced 2026-08-31 16:26:16 +00:00
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`.
This commit is contained in:
@@ -30,7 +30,7 @@ Applied in order: **YAGNI → KISS → DRY**.
|
||||
|
||||
### No hidden state
|
||||
|
||||
- Profile fetchers mutate the `*Profile` argument in-place (`FetchContributionsAllTime(p, opts)`) — explicit ownership, no package-level caches.
|
||||
- Profile fetchers mutate the `*Profile` argument in-place (`FetchContributionsAllTime(ctx, p, opts)`) — explicit ownership, no package-level caches.
|
||||
- Renderers are pure functions of `(*Profile, theme.Theme)`. No side effects, no goroutines.
|
||||
|
||||
### Comments
|
||||
@@ -58,7 +58,7 @@ Never write comments that describe what well-named code does (`// increment coun
|
||||
|
||||
- Always XML-escape user-controlled strings through `escapeXML` (`&`, `<`, `>`, `"`, `'`).
|
||||
- Numbers formatted via `formatInt` with thousands separators.
|
||||
- Stable viewbox per card (`500×220` for most, `500×220` for profile too).
|
||||
- Stable viewbox per card (`340×200`) matching github-profile-summary-cards.
|
||||
- No `<script>` tags, no event handlers. Cards are pure markup.
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -97,13 +97,14 @@ commitHistoryQuery ──► Productive + CommitsByLanguage (+ AllTime variants)
|
||||
|
||||
## Test coverage
|
||||
|
||||
- `internal/card/card_test.go` — RenderAll produces 9 valid SVGs; escape + formatInt spot-checks.
|
||||
- `internal/card/card_test.go` — `RenderAll` produces 9 valid SVGs; XML escape through real render pipeline; `formatInt` cases; `TestDonutSingleSlice` (guards the empty-arc regression); `TestDonutEmpty` (no-data fallback).
|
||||
- `internal/github/profile_test.go` — `sortLangStats` ordering and tiebreak.
|
||||
- `main_test.go` — `TestUTCOffsetLabel` covers UTC, Asia/Saigon, half-hour (Kolkata), quarter-hour (Kathmandu) zones.
|
||||
|
||||
No network-touching tests; real runs verified via `-token` + local build.
|
||||
|
||||
## Naming conventions
|
||||
|
||||
- Go files use snake_case for multi-word names (`repos_per_language.go`, `contributions_all_time.go`).
|
||||
- Cards' `Filename()` returns the numbered SVG output name — consumers sort lexicographically.
|
||||
- Cards' `Filename()` returns a plain kebab-case name (`profile-details.svg`, `most-commit-language-all-time.svg`, …). Embedders reference by name, so no numeric prefix is needed.
|
||||
- Themes in snake_case to match upstream (`github_dark`, `nord_bright`).
|
||||
|
||||
@@ -108,14 +108,20 @@ runs:
|
||||
2. `release.yml` runs `go vet` + `go test` as a gate before the docker and
|
||||
binaries jobs. If tests fail, no artifacts ship.
|
||||
3. On green, GHCR push + cross-platform binary artifacts happen automatically.
|
||||
4. Docker base images and third-party actions are SHA-pinned (with version
|
||||
4. The `update-major-tag` job force-moves the floating major tag (e.g. `v1`)
|
||||
to this release's commit after test + docker + binaries all pass.
|
||||
Consumers pinned to `tiennm99/ghstats@v1` pick up the release on their
|
||||
next Action run without a workflow edit.
|
||||
5. Docker base images and third-party actions are SHA-pinned (with version
|
||||
comments) so mutable-tag changes upstream can't rewrite a released image.
|
||||
5. **Marketplace publishing (one-time per repo):** GitHub only exposes the
|
||||
6. **Marketplace publishing (one-time per repo):** GitHub only exposes the
|
||||
"Publish this Action to the GitHub Marketplace" toggle on the Release
|
||||
web UI — there is no CLI flag. Open the newly created release at
|
||||
`https://github.com/tiennm99/ghstats/releases/tag/vX.Y.Z/edit`, tick the
|
||||
marketplace checkbox, accept the terms, and re-publish. Subsequent
|
||||
releases inherit marketplace visibility automatically.
|
||||
releases inherit marketplace visibility automatically. The Marketplace
|
||||
listing name is `ghstats-cards` (set in `action.yml`) because the bare
|
||||
`ghstats` is already taken on the Marketplace.
|
||||
|
||||
## Rollback
|
||||
|
||||
|
||||
+26
-26
@@ -1,17 +1,17 @@
|
||||
# Design Guidelines
|
||||
|
||||
Visual conventions for ghstats SVG cards. All cards share a single frame shape so they stack cleanly in a README.
|
||||
Visual conventions for ghstats SVG cards. All cards share a single frame shape so they stack cleanly in a README — two cards sit side-by-side inside GitHub's ~816 px content column.
|
||||
|
||||
## Card frame
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Width × Height | **500 × 220** |
|
||||
| Corner radius | 8 px |
|
||||
| Width × Height | **340 × 200** (matches github-profile-summary-cards) |
|
||||
| Corner radius | 6 px |
|
||||
| Stroke | `theme.Stroke` at `theme.StrokeOpacity` |
|
||||
| Fill | `theme.Background` |
|
||||
| Font family | `'Segoe UI', Ubuntu, Sans-Serif` |
|
||||
| Title | 18 px, weight 600, `theme.Title`, anchored at `(25, 35)` |
|
||||
| Title | 15 px, weight 600, `theme.Title`, anchored at `(20, 30)` |
|
||||
|
||||
Generated by `header(width, height, bg, stroke, strokeOpacity, titleColor, title)` in `internal/card/svg.go`.
|
||||
|
||||
@@ -32,30 +32,30 @@ Cards MUST NOT hardcode colors outside these fields. If a new visual needs a sha
|
||||
|
||||
Single-column rows of `icon + label` or `icon + label + value`.
|
||||
|
||||
| Metric | Value |
|
||||
| --- | --- |
|
||||
| First row baseline (y) | 70 |
|
||||
| Row spacing | 24 px |
|
||||
| Icon scale | `14/16 = 0.875` from 16×16 Octicon viewBox |
|
||||
| Icon color | `theme.Muted` |
|
||||
| Right-aligned value anchor (stats) | `x = 475`, `text-anchor="end"` |
|
||||
| Value font weight | 600 |
|
||||
| Value color | `theme.Accent` |
|
||||
| Metric | Profile | Stats |
|
||||
| --- | --- | --- |
|
||||
| First row baseline (y) | 60 | 55 |
|
||||
| Row spacing | 20 px | 20 px |
|
||||
| Row x padding | 20 | 20 |
|
||||
| Icon scale | `12/16 = 0.75` from 16×16 Octicon viewBox | same |
|
||||
| Icon color | `theme.Muted` | same |
|
||||
| Value font | 12 px, `theme.Text` | 12 px, weight 600, `theme.Accent`, right-anchored at `x = 320` |
|
||||
|
||||
Cap rows at what fits: 7 for profile, 7 for stats (commits row splits into lifetime + last-year).
|
||||
Cap rows at what fits: up to 7 rows per card. Stats splits commits into lifetime + last-year rows.
|
||||
|
||||
## Donut cards (language breakdowns)
|
||||
|
||||
| Metric | Value |
|
||||
| --- | --- |
|
||||
| Donut centre | `(380, 120)` |
|
||||
| Outer radius | 70 |
|
||||
| Inner radius | 38 |
|
||||
| Top-N entries shown | 6 (overflow collapses into "Other") |
|
||||
| Donut centre | `(250, 110)` |
|
||||
| Outer radius | 55 |
|
||||
| Inner radius | 30 |
|
||||
| Top-N entries shown | 5 (overflow collapses into "Other") |
|
||||
| Slice stroke | `theme.Background`, 1.5 px (gap between slices) |
|
||||
| Legend origin | `(30, 70)` |
|
||||
| Legend row height | 22 px |
|
||||
| Swatch size | 12 × 12 |
|
||||
| Legend origin | `(20, 55)` |
|
||||
| Legend row height | 20 px |
|
||||
| Swatch size | 10 × 10 |
|
||||
| Legend font | 11 px |
|
||||
|
||||
Language colors come from linguist via GraphQL (`repo.languages.edges[].node.color`). Missing colors fall back to `theme.Accent`.
|
||||
|
||||
@@ -65,8 +65,8 @@ When there's **exactly one slice** (one language at 100%), the renderer emits tw
|
||||
|
||||
| Metric | Value |
|
||||
| --- | --- |
|
||||
| Chart area | `x ∈ [50, 475]`, `y ∈ [60, 170]` (110 tall) |
|
||||
| Bars | 24 bars, 2 px gap |
|
||||
| Chart area | `x ∈ [35, 325]`, `y ∈ [45, 155]` (110 tall) |
|
||||
| Bars | 24 bars, 1 px gap |
|
||||
| Bar fill | `theme.Accent` |
|
||||
| Y-axis ticks | `niceTicks(max, 5)` — 1/2/5 × 10^k ladder |
|
||||
| X-axis labels | Hours 0, 6, 12, 18, 23 |
|
||||
@@ -78,7 +78,7 @@ When there's **exactly one slice** (one language at 100%), the renderer emits tw
|
||||
|
||||
| Metric | Value |
|
||||
| --- | --- |
|
||||
| Chart area | `x ∈ [35, 465]`, `y ∈ [60, 180]` (120 tall) |
|
||||
| Chart area | `x ∈ [28, 312]`, `y ∈ [45, 150]` (105 tall) |
|
||||
| Curve | Catmull-Rom → cubic Bezier (tension 0.5) |
|
||||
| Fill | `theme.Accent` at 25% opacity |
|
||||
| Stroke | `theme.Accent`, 2 px |
|
||||
@@ -93,7 +93,7 @@ Missing months in the `[first, last]` range are inserted as zero-count rows to k
|
||||
|
||||
- Sourced from [Primer Octicons](https://primer.style/octicons/) 16×16 set.
|
||||
- Stored as raw `<path d="…"/>` strings in `internal/card/icons.go`.
|
||||
- Rendered inside `<g transform="translate(x,y) scale(0.875)" fill="muted">…</g>`.
|
||||
- Rendered inside `<g transform="translate(x,y) scale(0.75)" fill="muted">…</g>` (scaled to fit the 12 px box).
|
||||
- Used: `iconRepos`, `iconCompany`, `iconLocation`, `iconClock`, `iconLink`, `iconPeople`, `iconStar`, `iconCommit`, `iconPR`, `iconIssue`, `iconReview`.
|
||||
|
||||
Add new icons by copying the `<path>` from Octicons and appending to `icons.go`. Keep them to the same 16×16 viewBox so the existing scale math applies.
|
||||
@@ -107,4 +107,4 @@ Add new icons by copying the `<path>` from Octicons and appending to `icons.go`.
|
||||
## Text overflow
|
||||
|
||||
- Long strings (bio, repo names) are **not truncated**; they're XML-escaped and printed as-is.
|
||||
- If a card looks crowded at 500 px width, that's a card design problem — fix the layout, not the data.
|
||||
- If a card looks crowded at 340 px width, that's a card design problem — fix the layout, not the data.
|
||||
|
||||
@@ -28,7 +28,7 @@ Distinguishing traits:
|
||||
- **Single binary**: no Node, no Ruby, no Docker needed for CLI usage.
|
||||
- **Seed-list sampling**: commit-history probes land on repos the user actually committed in (via `contributionsCollection.commitContributionsByRepository`), not top-starred or owned-only.
|
||||
- **All-time variants**: for every time-bounded card (most-commit-language, productive-time, contributions), there's a lifetime counterpart.
|
||||
- **Public-safe defaults**: forks and private repos are **off** by default; users opt in.
|
||||
- **Accurate defaults**: forks and private repos are **on** by default so cards reflect real activity; `-include-private` silently no-ops if the token lacks `repo` scope, so the default is safe for public runs too.
|
||||
|
||||
## Functional requirements
|
||||
|
||||
|
||||
+18
-6
@@ -26,7 +26,7 @@
|
||||
## Phase 4 — Accurate repo sampling (✅ done)
|
||||
|
||||
- Seed list built from `commitContributionsByRepository` across every active year.
|
||||
- `-include-forks` / `-include-private` visibility flags (default off).
|
||||
- `-include-forks` / `-include-private` visibility flags (defaults later flipped on in Phase 7).
|
||||
- `-top-repos` demoted to an optional cap (default 0 = unlimited).
|
||||
- Commit-history query takes `$owner` so forks and non-owned repos are probeable.
|
||||
|
||||
@@ -50,9 +50,21 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
|
||||
- Stats card label "Contributed to (non-fork)" corrected to "Contributed to" (the query doesn't filter forks).
|
||||
- Tests: fixed stale XML-escape assertion, added `TestDonutSingleSlice`, added `TestUTCOffsetLabel` for half-hour zones.
|
||||
|
||||
## Phase 7 — Release polish & Marketplace publish (✅ done)
|
||||
|
||||
- Visibility defaults flipped on: `-include-forks`, `-include-private` now default `true` (private silently no-ops if token lacks scope).
|
||||
- Output filenames dropped the numeric prefix: `0-profile-details.svg` → `profile-details.svg` etc. Embedders reference by name.
|
||||
- Card dimensions shrunk `500×220` → `340×200` to match github-profile-summary-cards so two cards fit per row in a README.
|
||||
- Action `action.yml` name set to `ghstats-cards` for Marketplace (the bare `ghstats` is taken); repo stays `tiennm99/ghstats`.
|
||||
- `v1.0.0`, `v1.1.0`, `v1.1.1` tagged and released. Prebuilt binaries (linux/darwin/windows × amd64/arm64) ship with each; Docker image pushed to `ghcr.io/tiennm99/ghstats`.
|
||||
- Floating `v1` major tag created; `release.yml` has an `update-major-tag` job that force-moves `v1` to the latest patch after test+docker+binaries pass, so consumers pinned to `tiennm99/ghstats@v1` auto-pick new releases.
|
||||
- README badges (Marketplace / Release / License) + direct Marketplace link for cross-navigation.
|
||||
- Repo topics expanded for Marketplace discoverability (`ghstats-cards`, `profile-readme`, `stats-cards`, etc.).
|
||||
- An attempted repo rename to `tiennm99/ghstats-cards` was committed and reverted (commits `399a3dc` + `8bd2128` on record) — GHCR path immutability and the cost of breaking pinned consumers outweighed the Marketplace-name cosmetic benefit.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — Per-commit file classification (planned)
|
||||
## Phase 8 — Per-commit file classification (planned)
|
||||
|
||||
**Goal**: fix the Markdown-blog misattribution case (and any repo where linguist's byte view disagrees with what files user actually edited).
|
||||
|
||||
@@ -64,7 +76,7 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
|
||||
|
||||
**Status**: designed, not implemented.
|
||||
|
||||
## Phase 8 — Partial bare clone for lifetime all-repo stats (planned)
|
||||
## Phase 9 — Partial bare clone for lifetime all-repo stats (planned)
|
||||
|
||||
**Goal**: lifetime language stats across **every** repo a user has committed in, without the 500-commits-per-repo cap.
|
||||
|
||||
@@ -76,7 +88,7 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
|
||||
|
||||
**Status**: researched only; behind `-deep` flag when landed.
|
||||
|
||||
## Phase 9 — User-configurable repo exclusion (planned)
|
||||
## Phase 10 — User-configurable repo exclusion (planned)
|
||||
|
||||
**Goal**: let users drop throwaway repos (experiments, forks they stashed) from stats without disabling forks globally.
|
||||
|
||||
@@ -86,7 +98,7 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
|
||||
|
||||
**Status**: pending user demand.
|
||||
|
||||
## Phase 10 — Expand ownerAffiliations (planned)
|
||||
## Phase 11 — Expand ownerAffiliations (planned)
|
||||
|
||||
**Goal**: catch work done in org repos where user is a collaborator, not owner (e.g., company monorepos).
|
||||
|
||||
@@ -104,7 +116,7 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
|
||||
| No real-time API | Scope: scheduled batch renderer, not a server |
|
||||
| No WakaTime integration | Out of scope — WakaTime cards already exist (athul/waka-readme, anmol098/waka-readme-stats) |
|
||||
| No heatmap (7×24) variant of productive time | Simplified to 24-hour bar chart to match reference project |
|
||||
| Hard width of 500 px per card | Keeps README layout predictable; customizing width would cascade through every chart math |
|
||||
| Hard width of 340 px per card | Matches github-profile-summary-cards; customising would cascade through every chart's geometry. |
|
||||
|
||||
## Tracked research reports
|
||||
|
||||
|
||||
Reference in New Issue
Block a user