Files
ghstats/docs/code-standards.md
T
tiennm99 7b91e73467 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

4.6 KiB
Raw Blame History

Code Standards

Principles

Applied in order: YAGNI → KISS → DRY.

  • No feature flags, no plugin systems, no abstractions for hypothetical callers.
  • One way to do a thing. A helper emerges only after the second call site, never before.
  • Three similar lines beats a premature abstraction. Extract when a fourth arrives.

Go conventions

File naming

  • Multi-word files use snake_case (Go ecosystem standard): repos_per_language.go, contributions_all_time.go.
  • Single-word files stay single-word: client.go, model.go, profile.go.
  • Test files adjacent to the unit under test with _test.go suffix.

Package structure

  • main.go at repo root — the CLI wrapper only.
  • All reusable code under internal/ so it can't accidentally become an API.
  • Each package owns one concern: github = network, card = render, theme = palette data.

Error handling

  • Wrap errors with fmt.Errorf("%w: …", err) when adding context; bare return when the caller already has enough context.
  • Sentinel errors (errors.New) only when callers need to type-check. We have none today — don't invent them.
  • Network/API errors in fetchers bubble up; main.go decides whether to exit or warn.

No hidden state

  • 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

Default: no comments. Exceptions:

  • Why non-obvious code is non-obvious. Example: the scaleFactor = 10_000 constant — a comment explains why fixed-point and not float.
  • Hidden invariant or constraint. Example: the comment on contributionYearQuery noting the maxRepositories: 100 cap.
  • Workaround for an upstream behavior. Example: clamping the current year's to to now so GitHub doesn't reject future timestamps.

Never write comments that describe what well-named code does (// increment counter). Never reference "the recent fix" or "for issue #42" — that belongs in git.

Function length

  • No hard limit. 200-line functions are fine when the logic is linear.
  • Extract a helper only when (a) the same shape repeats twice, or (b) a block needs an independent name to be read at the call site.

Exported vs unexported

  • Start unexported. Export only when a test file or another package needs the symbol.
  • Types on the public API: Profile, RepoInfo, LangStat, LangEdge, DailyContribution, FetchOptions, Client, Theme, Card.
  • Everything else stays lowercase.

SVG output standards

  • Always XML-escape user-controlled strings through escapeXML (&, <, >, ", ').
  • Numbers formatted via formatInt with thousands separators.
  • Stable viewbox per card (340×200) matching github-profile-summary-cards.
  • No <script> tags, no event handlers. Cards are pure markup.

Testing

  • Unit tests in the same package, no mocking of http.Client — we test rendering and pure helpers.
  • Network tests are omitted; integration verification is manual (./ghstats -token ...).
  • go vet ./... clean before every commit.

Commit conventions

  • Conventional commit prefixes: feat:, fix:, refactor:, docs:, test:, build:, ci:, chore:.
  • Scope is optional: refactor(card): ….
  • Never use chore: or docs: for .claude/ directory changes (project rule).
  • No AI references. No "generated by" footers. Keep the subject ≤ 72 chars.

Pre-commit checklist

go vet ./...
go test ./...
go build ./...

All three must pass. If a test is failing, fix the test before committing — don't skip.

Card review checklist (pre-release)

In addition to the compile/test gate above, any change that touches files under internal/card/ must be reviewed against docs/design-guidelines.md → "Fit-the-frame invariant" before merging. Specifically:

  • Render the dracula theme against the tiny / typical / adversarial profile fixtures and visually verify nothing overflows or overlaps.
  • No hard-coded dimensions that only work for the reviewer's own profile (names, digit counts, year spans).
  • Every right-anchored value or text-anchor="middle" element fits inside its column / safety margin.
  • The CI-built demo/<theme>/ gallery is the canonical "dracula on the author's profile" view; don't rely on a local run alone.

This is a hard gate: a card that overflows on a realistic profile does not ship, even if tests pass.

Dependency policy

  • Stdlib only. go.mod lists no require entries.
  • If a future feature needs a third-party module, add it in a dedicated commit with justification in the message.
  • Keep binary size under 15 MB.