- 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.
4.4 KiB
Design Guidelines
Visual conventions for ghstats SVG cards. All cards share a single frame shape so they stack cleanly in a README.
Card frame
| Property | Value |
|---|---|
| Width × Height | 500 × 220 |
| Corner radius | 8 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) |
Generated by header(width, height, bg, stroke, strokeOpacity, titleColor, title) in internal/card/svg.go.
Theme role mapping
| Theme field | Used for |
|---|---|
Title |
Card title text |
Text |
Primary content (values, names) |
Background |
Card fill + stroke around donut slices to separate colors |
Stroke + StrokeOpacity |
Card outline |
Muted |
Axis lines, axis labels, icons, legend metadata |
Accent |
Bars, area fills, stat values, fallback slice color |
Cards MUST NOT hardcode colors outside these fields. If a new visual needs a shade, pick the closest existing field — don't extend the schema without a strong reason.
Row-based cards (profile, stats)
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 |
Cap rows at what fits: 7 for profile, 7 for stats (commits row splits into lifetime + last-year).
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") |
| Slice stroke | theme.Background, 1.5 px (gap between slices) |
| Legend origin | (30, 70) |
| Legend row height | 22 px |
| Swatch size | 12 × 12 |
Language colors come from linguist via GraphQL (repo.languages.edges[].node.color). Missing colors fall back to theme.Accent.
When there's exactly one slice (one language at 100%), the renderer emits two concentric <circle> elements instead of a pie arc, because SVG's A command from point P back to the same P draws nothing. Regression guarded by TestDonutSingleSlice.
Bar-chart cards (productive time)
| Metric | Value |
|---|---|
| Chart area | x ∈ [50, 475], y ∈ [60, 170] (110 tall) |
| Bars | 24 bars, 2 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 |
| Axis caption | "hour of day" bottom-center |
| Title format | Commits by Hour (<window>, UTC±N.NN) |
| Hover | <title>HH:00 — N commits</title> inside each bar |
Area-chart cards (contributions)
| Metric | Value |
|---|---|
| Chart area | x ∈ [35, 465], y ∈ [60, 180] (120 tall) |
| Curve | Catmull-Rom → cubic Bezier (tension 0.5) |
| Fill | theme.Accent at 25% opacity |
| Stroke | theme.Accent, 2 px |
| Y-axis | Both sides, mirrored, same tick values |
| X-axis labels | mm/yy, stride-thinned to ~6 labels regardless of bucket count |
| Axis caption | "mm/yy" bottom-center |
| First/last labels | Always printed (pinned endpoints) |
Missing months in the [first, last] range are inserted as zero-count rows to keep the curve time-continuous.
Icons
- Sourced from Primer Octicons 16×16 set.
- Stored as raw
<path d="…"/>strings ininternal/card/icons.go. - Rendered inside
<g transform="translate(x,y) scale(0.875)" fill="muted">…</g>. - 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.
Accessibility
- Contrast is the theme author's responsibility — we don't validate at runtime.
- Tooltips (
<title>) on productive-time bars let screen readers announce counts. - No motion, no
<animate>elements — profile READMEs render statically.
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.