# resume.github.com Administrator & Developer Guide

This guide is the technical companion to the User Guide. It is written for
forkers, self-hosters, and contributors who want to understand how
`resume/resume.github.com` is built, how to deploy it, what its security
boundary actually is, and where every behaviour lives in source. The
project is small — twenty-one source files, one JavaScript module of
roughly nineteen kilobytes, and a two-line Rack runner — but the surface
the JavaScript exercises against the GitHub REST API is non-trivial. This
document walks every piece.

The guide is pinned to commit `f599075` (5 August 2016). The repository
has been dormant since that commit; the live site at `resume.github.com`
runs the same code.

> [!NOTE]
> Because this repository contains no `package.json`, no Gemfile, and no
> dependency manifest, every external library is loaded by the HTML
> templates from a CDN at runtime. Treat the rendered DOM, not the file
> tree, as the source of truth for which JavaScript runs in production.

## 1. Architecture Overview

`resume.github.com` is a **client-only static web application**. There is
no server-side application logic in the repository, and no first-party
backend in production. The deployable artefact is a directory of static
files that any HTTP server can serve verbatim.

### What the system does

1. Serve `index.html` over HTTP (or HTTPS, in production via GitHub
   Pages) along with `js/githubresume.js`, the two CSS files in `css/`,
   and the static images in `images/`.
2. When the browser loads the page, `js/githubresume.js` parses
   `window.location.search` to extract a username.
3. If a username is present, the script verifies the user has starred
   `resume/resume.github.com` (synchronous AJAX), then issues parallel
   asynchronous JSONP calls to `api.github.com` for profile, repos,
   merged PRs, and orgs.
4. Each response is fed into a Mustache template loaded from `views/` via
   AJAX; the rendered HTML is injected into the `#resume` host element
   inside `index.html`.
5. The page can be printed to PDF via the browser's native print
   dialog; `css/print.css` controls the print-only styling.

There is no persistence, no session, no authentication, and no server-side
rendering. Every render is fresh, sourced live from the GitHub REST API.

### High-level architecture

```text
                         Visitor's browser
        ┌─────────────────────────────────────────────────────┐
        │                                                     │
        │   index.html  ──(loads)──► js/githubresume.js       │
        │       │                          │                  │
        │       │ <div id="resume">        │                  │
        │       │                          │ JSONP calls      │
        │       │                          ▼                  │
        │       │                  ┌──────────────┐           │
        │       │                  │ api.github   │           │
        │       │ AJAX HTML        │ .com         │           │
        │       ▼                  └──────────────┘           │
        │   views/*.html                  │                   │
        │   (Mustache)                    ▼                   │
        │       │                  Profile / repos /          │
        │       └─► render ◄───── PRs / orgs JSON             │
        │                                                     │
        └────────────────────┬────────────────────────────────┘
                             │ HTTP GET
                             ▼
                   resume.github.com
                   (static GitHub Pages)
```

### Component inventory

| Component | Path | Role |
|-----------|------|------|
| Entry HTML | `index.html` | Boots jQuery, Mustache, and `js/githubresume.js`; provides the `#resume` host element. |
| Front-end controller | `js/githubresume.js` | Orchestrates URL parsing, opt-in check, GitHub API calls, and Mustache rendering. |
| Page styling | `css/resume.css` | Screen layout, colours, typography. |
| Print styling | `css/print.css` | `@media print` overrides for clean PDF output. |
| Landing template | `views/index.html` | Default screen with a username input, shown when no query string is present. |
| User résumé template | `views/resume.html` | Main résumé layout for `data.type === 'User'`. |
| Org résumé template | `views/resumeOrgs.html` | Variant layout for `data.type === 'Organization'`. |
| Job row template | `views/job.html` | Single-repository row in the *Featured Projects* block. |
| Org row template | `views/org.html` | Single-organisation row in the *Organisations* block. |
| Contribution row template | `views/contrib.html` | Single-row entry in the *Open-source Contributions* block. |
| Error template | `views/error.html` | Generic error screen, bound to `window`'s `error` event. |
| API-limit template | `views/api_limit.html` | Shown when the starred-repos call returns HTTP 403. |
| Not-found template | `views/not_found.html` | Shown when the starred-repos call returns HTTP 404. |
| Opt-out template | `views/opt_out.html` | Shown when the user has not starred the repo. |
| Static assets | `images/` | `favicon.png`, `loader.gif`, `low_contrast_linen.png`, `srt.png`, `ttab.png`. |
| Rack runner | `config.ru` | Two-line Rack file used to serve the directory locally with `rackup`. |
| Repo readme | `README.markdown` | Repo-level introduction (the briefing reports it as not extracted, so do not assume specific content). |

### Technology stack

| Layer | Technology | Notes |
|-------|------------|-------|
| Markup | HTML5 | Templates use Mustache `{{ }}` placeholders. |
| Styling | CSS3 | Two stylesheets, no preprocessor. |
| Scripting | Vanilla JavaScript ES5 | No bundler, no transpilation. |
| DOM/AJAX library | jQuery | Loaded by the HTML templates from a CDN; not vendored in the repo. |
| Templating | Mustache.js | `Mustache.to_html()` rendered client-side. Loaded from a CDN. |
| Analytics | Google Analytics (`UA-21222559-1`) | Classic `ga.js` loader; protocol switches based on page protocol. |
| Local serving | Rack / `config.ru` | Optional — any static server suffices. |
| Hosting (production) | GitHub Pages | Inferred from the `resume.github.com` domain pattern; not encoded in the repo. |

> [!NOTE]
> jQuery and Mustache are referenced by the HTML templates but are not
> committed to the repository. The exact versions used in production
> depend on whatever the templates `<script>`-tag in. Treat any
> assertion about jQuery version as something you must verify by
> inspecting the rendered `index.html` in the browser.

### Data flow — the rendering pipeline

```text
URL query string
      │
      ▼
parse window.location.search
      │
      ▼
$(document).ready
      │
      ├─ no username ──► home() ──► AJAX views/index.html ──► #resume
      │
      └─ has username ──► run()
                          │
                          ▼
            github_user_starred_resume()  (synchronous)
                          │
              ┌───────────┼───────────┬──────────────┐
              │ 403       │ 404       │ false (no    │ true
              ▼           ▼           ▼ star)        ▼
       api_limit.html  not_found.html  opt_out.html  ── parallel calls ──
                                                       │
                                  ┌────────────────────┼──────────────────┐
                                  ▼                    ▼                  ▼
                            github_user        github_user_repos    github_user_orgs
                                  │                    │                  │
                                  │             github_user_issues        │
                                  ▼                    ▼                  ▼
                            views/resume.html    views/job.html     views/org.html
                            views/resumeOrgs.html views/contrib.html
                                  │                    │                  │
                                  └────────────────────┴──────────────────┘
                                                       │
                                                       ▼
                                                  #resume DOM
                                                       │
                                                       ▼
                                          Print button bound → window.print()
```

## 2. System Requirements

The project has minimal infrastructural requirements. They are different
for visitors, for self-hosting on GitHub Pages, and for running locally.

### For visitors (hosted site)

| Requirement | Detail |
|-------------|--------|
| Browser | Any modern browser with JavaScript and cookies enabled. |
| Network | Outbound HTTPS to `resume.github.com`, `api.github.com`, and `*.google-analytics.com`. |
| GitHub account | Required for the **subject** of the résumé (the user being rendered must have starred the repo). Visitors do not need an account. |

### For local development

| Requirement | Detail |
|-------------|--------|
| OS | Anything (Linux, macOS, Windows, BSD). The project is platform-neutral static files. |
| HTTP server | Anything that serves static files: Ruby + Rack, Python's `http.server`, Node's `http-server`, nginx, Apache, Caddy. |
| Disk | Less than 1 MiB for the entire repository. |
| RAM | Negligible — the server only serves static bytes. |
| Internet | Required at runtime so the browser can reach `api.github.com`. |

### For production hosting

| Requirement | Detail |
|-------------|--------|
| Static-file host | GitHub Pages, S3 + CloudFront, Netlify, Vercel, Cloudflare Pages, or any equivalent. |
| TLS certificate | Strongly recommended (Google Analytics protocol switching depends on `https`). |
| DNS | An apex or subdomain pointing to the static host (the upstream uses `resume.github.com`). |

> [!IMPORTANT]
> Because the front-end calls `api.github.com` directly from the
> browser, your hosting environment must allow third-party CORS. GitHub's
> public API explicitly allows cross-origin requests; no proxy is
> required. If you put the site behind a CSP, you must allow
> `connect-src https://api.github.com`.

## 3. Configuration Reference

This section documents every configurable surface in the project. The
list is short by design: the upstream code prefers hard-coded constants
and JavaScript defaults over external configuration.

### Configuration files

| File | Format | Location | Purpose |
|------|--------|----------|---------|
| `config.ru` | Ruby (Rack) | Repo root | One-shot Rack runner used by `rackup` to serve the directory locally. Two lines: `require 'rack'` and a `Rack::Directory` mount. Not used in production. |

There is no `package.json`, no `Gemfile`, no `.env`, no YAML config, no
JSON config, and no environment variables. The AST extractor confirms
**zero configuration keys** in the repository.

### Environment variables

The project reads no environment variables from the JavaScript runtime
(the browser does not have access to host environment variables, and the
front end does not consult any). If you self-host behind nginx, you may
set environment variables for nginx itself; those are out of scope for
this repository.

### Hard-coded constants in `js/githubresume.js`

These are the only knobs an operator can turn without rewriting the
front-end. All live in `js/githubresume.js`. Edit the file directly.

| Constant | Default | Lines | Description | Required? |
|----------|---------|-------|-------------|-----------|
| `trackerId` | `'UA-21222559-1'` | top of file | Google Analytics property ID. The trailing `if (trackerId)` block injects the GA loader only when this is truthy. | No — set to `''` or remove to disable analytics. |
| `username` | `undefined` (set at runtime) | top of file | Holds the parsed query-string user. Do not edit. | N/A |
| `urlParams` | `{}` | top of file | Holds the parsed query string. Do not edit. | N/A |
| `maxItems` | `5` | inside `run()` | Maximum number of featured projects rendered. Also used as the soft cap for organisations (shared `itemCount`). | No |
| `maxLanguages` | `9` | inside `run()` | Maximum number of language entries displayed. | No |
| `COEF_REPOS` | `2` | inside `getUserStatus()` | Weight applied to `public_repos`. | No |
| `COEF_GISTS` | `0.25` | inside `getUserStatus()` | Weight applied to `public_gists`. | No |
| `COEF_FOLLOWERS` | `0.5` | inside `getUserStatus()` | Weight applied to `followers`. | No |
| `COEF_FOLLOWING` | `0.25` | inside `getUserStatus()` | Weight applied to `following`. | No |
| `FIRST_STEP` | `0` | inside `getUserStatus()` | Score threshold below which the *Inactive* label applies. | No |
| `SECOND_STEP` | `5` | inside `getUserStatus()` | Upper bound for *Newbie*. | No |
| `THIRD_STEP` | `20` | inside `getUserStatus()` | Upper bound for *Regular*. | No |
| `FOURTH_STEP` | `50` | inside `getUserStatus()` | Upper bound for *Advanced*. | No |
| `FIFTH_STEP` | `150` | inside `getUserStatus()` | Upper bound for *Enthusiastic*. Above this, *Passionate*. | No |
| `EXTRA_POINT_GAIN` | `1` | inside `getUserStatus()` | Bonus added when the user is an early adopter, or when location, email, and blog are all set. Awarded twice (once for each condition). | No |

> [!TIP]
> If you fork the project, the most common single change is to set
> `trackerId` to your own Google Analytics property (or to an empty
> string to disable analytics). The second most common change is to
> raise `maxItems` from `5` for users with extensive portfolios.

### Runtime URL inputs

| Input | Source | Behaviour |
|-------|--------|-----------|
| First query-string key | `window.location.search` | Used as the GitHub username. The parser walks `?key=value&key=value` and assigns the first key it sees to `urlParams[0]`, which is then read as `username`. |

> [!IMPORTANT]
> The query-string parser uses `urlParams[0] = d(e[1]);` inside the
> `while (e = r.exec(q))` loop. Because the assignment is to index `0`
> on every iteration, only the **last** parameter parsed actually
> survives. In practice, with a single-key URL like `?octocat`, this
> works as intended; with multi-key URLs, behaviour is non-obvious.

### Templates

The Mustache templates in `views/` accept these named variables:

#### `views/resume.html` and `views/resumeOrgs.html`

| Variable | Source | Notes |
|----------|--------|-------|
| `name` | `data.name` ?? `data.login` | Display name. |
| `type` | `data.type` | `User` or `Organization`. |
| `email` | `data.email` | May be `null`. |
| `created_at` | `data.created_at` | ISO timestamp. |
| `earlyAdopter` | `0` or `1` | Computed locally. |
| `location` | `data.location` | May be `null`. |
| `gravatar_id` | `data.gravatar_id` | Legacy, may be empty. |
| `avatar_url` | Computed | Rewritten for organisations to a Gravatar URL. |
| `repos` | `data.public_repos` | Count. |
| `reposLabel` | `repository` / `repositories` | Singular/plural form. |
| `followers` | `data.followers` | Count. |
| `followersLabel` | `follower` / `followers` | Singular/plural form. |
| `username` | URL parameter | Echoed for self-links. |
| `userStatus` | `getUserStatus()` | One of six labels (see the User Guide). |
| `since` | Computed | Year, or `'this year'` / `'last year'`. |
| `resume_url` | `window.location` | Echoed for QR codes / sharing. |
| `website` | `data.blog`, prefixed with `http://` if missing scheme | Optional. |

#### `views/job.html`

| Variable | Source |
|----------|--------|
| `name`, `date`, `language`, `description`, `homepage`, `username`, `watchers`, `forks`, `watchersLabel`, `forksLabel`, `last` | All derived from `repo.info` and rendering loop state. |

#### `views/contrib.html`

| Variable | Source |
|----------|--------|
| `count` | Number of merged PRs in the repo. |
| `username` | URL parameter. |
| `repoUrl` | Rewritten from `repository_url`. |
| `repoName` | Rewritten from `repository_url`. |
| `commitsUrl` | `<repoUrl>/commits?author=<username>`. |

#### `views/org.html`

| Variable | Source |
|----------|--------|
| `name` | `org.info.name` ?? `org.info.login`. |
| `now` | `new Date().getFullYear()`. |
| `last` | Set on the final iteration. |

## 4. Deployment & Scaling

The deployable artefact is the entire repository. There is nothing to
build, transpile, or bundle.

### Path A — GitHub Pages (matches upstream)

The upstream production site is `resume.github.com`. The conventional
GitHub Pages deploy is:

1. Fork or clone the repository.
2. Push the contents to a branch named `master` (or whatever your
   GitHub Pages source is set to).
3. In the repository settings, enable **Pages** with the source set to
   the chosen branch and the directory set to root (`/`).
4. Configure your custom domain in **Pages → Custom domain** and set up
   the DNS A or CNAME records as GitHub instructs. Apex domains use A
   records to GitHub Pages IPs; subdomains use a CNAME to
   `<owner>.github.io`.
5. Wait for the certificate to provision, then enable **Enforce HTTPS**.

> [!NOTE]
> A `CNAME` file at the repo root would be required for a custom domain
> on GitHub Pages, but the upstream commit `f599075` does not include
> one — the upstream relies on the implicit `<owner>.github.io` mapping
> for the `resume` organisation.

### Path B — Object storage + CDN

Any static-file host works. The pattern is identical regardless of
provider:

```bash
# Example: AWS S3 + CloudFront
aws s3 sync . s3://your-bucket/ --exclude '.git/*' --exclude 'README.markdown'
aws cloudfront create-invalidation \
    --distribution-id E1XXXXXXX --paths '/*'
```

```text
upload: ./index.html to s3://your-bucket/index.html
upload: ./js/githubresume.js to s3://your-bucket/js/githubresume.js
upload: ./css/resume.css to s3://your-bucket/css/resume.css
... (and so on for the remaining static files)
```

### Path C — A single Docker container

The project does not ship a `Dockerfile`. If you want a containerised
version, point any nginx-based image at the repository directory:

```dockerfile
FROM nginx:alpine
COPY . /usr/share/nginx/html
EXPOSE 80
```

```bash
docker build -t resume-github-com .
docker run --rm -p 8080:80 resume-github-com
```

> [!TIP]
> For local development on Windows, the simplest path is `python -m http.server`
> from the repository root — no Docker, no Ruby, no Node required, and
> Python ships in modern Windows installations.

### Scaling considerations

Because every request is for static bytes, scaling is trivial — any CDN
in front of the storage will handle effectively unlimited concurrency.
The interesting bottleneck is on the **client side**: the GitHub API
rate limit caps how many concurrent visitors any single IP can serve.

| Bottleneck | Where | Mitigation |
|------------|-------|------------|
| 60-req/hour limit (unauthenticated) | `api.github.com` | None applied in the upstream. Forks add tokens at their own risk (see Security). |
| Search API quota | `api.github.com/search/issues` | Same — the search endpoint has its own limits. |
| Synchronous starred-repos call | `github_user_starred_resume()` | Blocks the page; large user star lists cascade additional 100-per-page calls until the repo is found or the list ends. |
| Mustache rendering | Browser CPU | Negligible for typical résumé sizes. |

## 5. Operations

There is essentially no operational surface for the upstream — once
deployed to a static host, the site runs without maintenance. The
operational tasks below apply to forks and self-hosters.

### Routine maintenance checklist

- [ ] Rotate Google Analytics property if you are tracking your own deployment.
- [ ] Audit dependencies referenced by `index.html` and the templates (jQuery, Mustache CDNs).
- [ ] Bump the year displayed by `views/org.html` if your fork uses a static template instead of the runtime `now` value.
- [ ] Re-test against the GitHub API at least once a year — GitHub's REST API has continued to change since 2016 (legacy `data` envelope, gravatar IDs, etc.).
- [ ] Verify the site still loads correctly after browser updates that may deprecate JSONP.

### Health checks

The site has no explicit health-check endpoint. The closest equivalent
is a request for `/` returning HTTP 200 with the literal string
`<title>Resume</title>` in the HTML, which can be verified with a curl
probe:

```bash
curl -sf https://your-host/ | grep -q '<title>Resume</title>' && echo OK || echo FAIL
```

```text
OK
```

### Logging

The upstream has no first-party logging. Three signals exist:

1. **`console.log(err)`** inside `$(document).ready` — only visible in
   the visitor's browser console. Surfaces unhandled errors from the
   `run()` pipeline.
2. **Google Analytics** — page views and (if you extend the GA block)
   custom events.
3. **HTTP access logs** from your static host (GitHub Pages, S3,
   nginx).

> [!NOTE]
> The error template (`views/error.html`) is loaded by the global
> `window` `error` event handler bound at the very bottom of
> `js/githubresume.js`: `$(window).bind('error', error);`. This means
> any uncaught script error will replace the page contents with the
> error template; you will not see the original error UI. For
> debugging, **disable** the `bind('error', error)` line locally so the
> original page state survives.

### Monitoring recommendations

- Synthetic check: probe `/` once per hour from an external monitor.
- Synthetic render check: load `https://your-host/?known-username` in a
  headless browser weekly and assert the rendered DOM contains the
  expected username.
- API health: monitor `https://api.github.com/users/octocat` from your
  monitoring source. If GitHub returns 429s widely, the site will
  surface API-limit screens to many visitors regardless of the local
  state.

## 6. Performance

### Page-load profile

| Phase | Approx. duration | Notes |
|-------|------------------|-------|
| HTML download | <100 ms (CDN) | `index.html` is small. |
| jQuery + Mustache | 100–300 ms | Depends on the CDN used by the templates. |
| `js/githubresume.js` | <50 ms parse | ~19 KB minified would be smaller, but the upstream commits the full unminified source. |
| Star check (synchronous) | 200–800 ms | First page of starred repos. Adds ~200 ms per additional 100 starred repos until the project repo is found. |
| Profile / repos / PRs / orgs | 200–600 ms each | Run in parallel, so the bottleneck is the slowest of the four. |
| Mustache render | <50 ms | Trivial DOM injection. |

The dominant variable is the **synchronous** star check. Users who star
many repositories will experience longer page loads on first render.

### Optimisation ideas (not in upstream)

- Replace the synchronous `$.ajax({ async: false })` call with an
  asynchronous call and use a loader spinner instead of blocking the
  thread.
- Cache the GitHub API responses in `localStorage` keyed by username +
  date, with a five-minute TTL.
- Defer Google Analytics with `async`/`defer` script attributes.
- Strip unused images from the deployment (`images/srt.png`,
  `images/ttab.png` are referenced by templates; verify before
  removing).

> [!TIP]
> If you fork and want to make the page faster without rewriting the
> star-check, simply move the call to async and show the loader image
> from `images/loader.gif` in the meantime. The synchronous design was
> a pragmatic 2016 choice that a modern browser flags as a performance
> warning.

## 7. Security

Security analysis for `resume.github.com` is unusually simple because
there is no backend, no authentication, and no first-party data store.
The threat surface is the GitHub REST API and the static files
themselves. This section covers all of it.

### Trust boundary

The browser running `js/githubresume.js` is the trust boundary. All data
displayed comes from one of three sources:

1. **The static files** served by your host — trusted.
2. **GitHub's REST API** (`api.github.com`) — trusted up to whatever
   public profile data GitHub returns for the named user.
3. **The URL query string** controlled by the visitor — *untrusted*.

### What the project does NOT expose

- No personally-identifying information beyond what GitHub already
  exposes publicly via the REST API.
- No private repositories, private gists, private email addresses, or
  authenticated organisation details.
- No first-party cookies (Google Analytics drops its own).
- No POST endpoints. Every API call is GET, and the project does not
  ship a single line of mutation logic.

### What the project DOES expose

- The `?username` query parameter is a public input that any visitor
  can manipulate. The username flows directly into multiple URLs:

  ```javascript
  url = 'https://api.github.com/users/' + username + '/repos?per_page=100&callback=?'
  ```

  GitHub's API performs its own input validation server-side, so a
  malicious username (e.g. URL-encoded special characters) will most
  likely return a 404 rather than reflect into anything dangerous.

- The `username` value is also injected into rendered HTML via Mustache:

  ```javascript
  toString: function() {
      return '<a href="https://github.com/search?q=user%3A'
          + username + '&l=' + encodeURIComponent(this.name) + '">'
          + this.name + '</a>';
  }
  ```

  Mustache by default escapes interpolated `{{ }}` values. However, the
  `toString()` callback above builds the link with **string
  concatenation**, so the username is **not** escaped at this point.

> [!WARNING]
> The string-concatenated `<a href="...?q=user%3A' + username + …'">`
> path is a potential reflected-XSS vector if `username` ever contains
> a quote character. In practice, GitHub usernames are restricted to
> `[A-Za-z0-9-]`, so this is a latent risk rather than an exploitable
> one — but a fork that loosens the input contract should add explicit
> escaping. Use `$('<a/>').attr('href', …).text(name)` instead of
> string concatenation.

### GitHub API rate-limit and token handling

The upstream makes **all** GitHub API calls **unauthenticated**. There
is no token in the source, no token-passing UI, and no localStorage key
for one. Operators forking the project sometimes consider adding a
personal-access token to raise the rate limit; this is **strongly
discouraged for any public deployment**.

> [!CAUTION]
> Embedding a personal-access token in client-side JavaScript is
> equivalent to publishing the token. Anyone who views the page source
> can extract and use it. Even a token scoped to `public_repo` is a
> real account credential and is logged in GitHub's audit trail. Do
> not do this. If you need authenticated rate limits, build a
> server-side proxy with a token stored in environment variables and
> forward only specific GET endpoints; do not expose the token to the
> browser.

### Public-data-only assumption

The project assumes every input maps to a public GitHub user. There is
no permission model beyond GitHub's own — if a user's profile is
private, the API will return 404 and the site will show the *Not found*
template. Private repositories, gists, and PRs are simply absent from
the API responses, so the rendered résumé reflects only what GitHub
already publishes openly.

### Content Security Policy

There is no `<meta http-equiv="Content-Security-Policy">` in the
repository. A reasonable hardened CSP for a fork would be:

```text
default-src 'self';
script-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com
           https://www.google-analytics.com https://ssl.google-analytics.com;
style-src 'self' 'unsafe-inline';
img-src 'self' https://avatars.githubusercontent.com
           https://secure.gravatar.com https://assets-cdn.github.com
           https://www.google-analytics.com data:;
connect-src https://api.github.com https://www.google-analytics.com;
```

> [!IMPORTANT]
> The `script-src` directive must explicitly allow whichever CDN your
> templates load jQuery and Mustache from. The upstream does not name a
> specific CDN — inspect the rendered HTML to discover the URLs in
> production.

### Things you should NOT expose

- A token of any kind in client-side JS or in the static files.
- A custom backend that proxies the GitHub API without rate limiting,
  request-size limits, and abuse logging — you will become a botnet
  amplifier.
- Visitor IP addresses or query strings beyond what your hosting access
  log already records. Do not add custom analytics that capture the
  `?username` parameter for users who did not opt in.

### Dependencies — supply-chain considerations

The repository commits no JavaScript dependencies. They are loaded at
runtime by the HTML templates from CDNs. In 2026, that means:

- jQuery — pin to a known-good version on a trusted CDN, ideally with
  Subresource Integrity (`integrity="sha384-..."`).
- Mustache — same.
- `https://www.google-analytics.com/ga.js` — the **classic** GA loader,
  deprecated by Google. Replace with `gtag.js` or remove entirely on
  any new deployment.

> [!CAUTION]
> The upstream `ga.js` loader is loaded over both HTTP and HTTPS
> depending on the page protocol, with no Subresource Integrity. A
> compromise of the GA CDN or a downgrade attack on a non-HTTPS host
> would let an attacker run arbitrary JavaScript in the user's browser
> session. **Always serve over HTTPS** and consider removing the
> classic GA loader on modern forks.

## 8. Developer Internals

This section is the source-level reference. It covers structure, code
patterns, function-by-function semantics, and a contributing guide.

### Repository structure

```
resume.github.com/
├── README.markdown          ── Repository introduction (briefing did not extract its content)
├── config.ru                ── Two-line Rack runner for local dev
├── index.html               ── Page shell, hosts <div id="resume">
├── css/
│   ├── print.css            ── @media print overrides
│   └── resume.css           ── Screen styling
├── images/
│   ├── favicon.png          ── Browser tab icon
│   ├── loader.gif           ── Spinner shown during async loads
│   ├── low_contrast_linen.png  ── Background texture
│   ├── srt.png              ── Asset referenced by template (verify before removing)
│   └── ttab.png             ── Asset referenced by template (verify before removing)
├── js/
│   └── githubresume.js      ── The single front-end module
└── views/
    ├── api_limit.html       ── Shown on 403 from GitHub API
    ├── contrib.html         ── Single contribution row template
    ├── error.html           ── Generic error screen
    ├── index.html           ── Default landing screen
    ├── job.html             ── Single project row template
    ├── not_found.html       ── Shown on 404 from GitHub API
    ├── opt_out.html         ── Shown when subject has not starred the repo
    ├── org.html             ── Single organisation row template
    ├── resume.html          ── User résumé layout
    └── resumeOrgs.html      ── Organisation résumé layout
```

> [!NOTE]
> `views/index.html` and the top-level `index.html` are different
> files. The top-level `index.html` is the page shell that boots
> JavaScript. The `views/index.html` is the partial Mustache template
> that the `home()` function loads into the `#resume` div when no
> username is supplied. Don't confuse them.

### Module reference — `js/githubresume.js`

The entire front end lives in this one ~19 KB module. It is structured
as a top-level IIFE that parses the URL, then a `$(document).ready`
handler that branches on whether a username is present. Below is every
function, in source order.

#### URL parser IIFE (top of file)

Anonymous immediately-invoked function expression (IIFE). Walks
`window.location.search` once and stores the **last** parsed key in
`urlParams[0]`. The variable is read as the username when
`$(document).ready` fires.

```javascript
var urlParams = {};
(function () {
    var e, a = /\+/g, r = /([^&=]+)=?([^&]*)/g,
        d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
        q = window.location.search.substring(1);
    while (e = r.exec(q)) {
       urlParams[0] = d(e[1]);
    }
})();
```

> [!NOTE]
> The assignment `urlParams[0] = d(e[1]);` overwrites on every loop
> iteration. With a single key like `?octocat`, this is fine. With
> `?a=1&b=2`, `urlParams[0]` ends as `b`. This is a pre-existing
> behaviour and not a bug to fix without coordination — many forks rely
> on the single-key idiom.

#### `error()`

Loads `views/error.html` into the `#resume` element. Bound to the
global `window` `error` event at the bottom of the file:
`$(window).bind('error', error);`. Any unhandled JavaScript error
replaces the page contents with this template.

#### `home()`

Loads `views/index.html` into the `#resume` element. Called when no
username is supplied.

#### `github_user(username, callback)`

JSONP fetch of `https://api.github.com/users/<username>?callback=?`.
Calls back with the entire response object. Note that GitHub's legacy
JSONP responses wrap the user record in a `data` envelope; the
downstream consumer accesses `data.data.created_at`, `data.data.name`,
etc.

#### `github_user_repos(username, callback, page_number, prev_data)`

Recursive paginated JSONP fetch of
`https://api.github.com/users/<username>/repos?per_page=100`. Continues
to the next page when `repos.data.length == 100`, calls back with the
accumulated array when shorter. Pages are 1-indexed; the recursion
appends `&page=<n>` for pages > 1.

#### `github_user_issues(username, callback, page_number, prev_data)`

Recursive paginated JSONP fetch of
`https://api.github.com/search/issues?q=type:pr+is:merged+author:<user>&per_page=100`.
Note the recursion condition `if (repos.data.total_count == 100)` is
unusual — it tests the **total** count, not the **page** count, which
will recurse forever if the user has exactly 100 merged PRs total.

> [!WARNING]
> The merged-PRs paginator has a known bug: the stop condition
> `repos.data.total_count == 100` should be `repos.data.items.length == 100`.
> Forks should fix this; the upstream behaviour is to recurse until the
> server returns an empty page or returns a different total. Verify the
> behaviour with users in the 100-PR range before relying on it.

#### `github_user_orgs(username, callback)`

Single JSONP fetch of `https://api.github.com/users/<username>/orgs?callback=?`.
No pagination.

#### `github_user_starred_resume(username, page)`

**Synchronous** AJAX (`async: false`) fetch of
`https://api.github.com/users/<username>/starred?per_page=100&page=<n>`.
This is the function that blocks the page during the opt-in check.

Behaviour:

- Iterates each repo in the response.
- Returns `true` immediately if it sees `repo.full_name == "resume/resume.github.com"`.
- Returns the string `'api_limit'` on HTTP 403.
- Returns the string `'not_found'` on HTTP 404.
- Recurses with `page + 1` if the page returned exactly 100 entries.
- Returns `false` once the recursion exhausts all pages without
  finding the project repo.

#### `run()`

The orchestrator. Called from `$(document).ready` when a username is
present. Performs the synchronous star check, branches to `api_limit.html`,
`not_found.html`, or `opt_out.html` if the check returns a non-truthy
result, otherwise launches the four parallel JSONP calls
(`github_user`, `github_user_repos`, `github_user_issues`,
`github_user_orgs`) and wires their results into Mustache templates.
Inside `run()` is also the nested `getUserStatus()` function described
in §3.

The Mustache template chosen for the main résumé block depends on
account type:

```javascript
var resume = (data.type == 'User' ? 'views/resume.html' : 'views/resumeOrgs.html');
```

#### Google Analytics block (bottom of file)

Classic `ga.js` loader. Switches between
`https://ssl.google-analytics.com/ga.js` and
`http://www.google-analytics.com/ga.js` based on `document.location.protocol`.
Skipped entirely when `trackerId` is falsy.

### Code patterns and conventions

| Pattern | Where it appears | Notes |
|---------|------------------|-------|
| IIFE for parser | URL parser | Common 2016-era idiom. |
| Recursive pagination via callback | `github_user_repos`, `github_user_issues`, `github_user_starred_resume` | All three use the same shape: pass `page_number` and `prev_data` through, recurse on a length-based stop condition. |
| Mustache `to_html(template, view)` | All template rendering | Loaded as a global. |
| `$('#id').html(data)` | Template injection | Replaces the inner HTML of the host element. |
| Magic strings for state | `'api_limit'`, `'not_found'` | Returned from `github_user_starred_resume` and re-checked in `run()`. |

### Known issues and tech debt

- **Synchronous AJAX in `github_user_starred_resume`** — blocks the
  page; modern browsers print a deprecation warning.
- **Pagination bug in `github_user_issues`** — uses `total_count`
  instead of items length.
- **Shared `itemCount` between projects and orgs** — silently truncates
  organisations.
- **`urlParams[0]` overwritten in a loop** — only the last key
  survives.
- **Classic GA `ga.js` loader** — long deprecated by Google in favour
  of `gtag.js` / GA4.
- **No package manifest** — jQuery and Mustache versions are
  uncontrolled (loaded from whatever CDN the templates reference).
- **No `CNAME` file** in the repo, despite production use of a custom
  domain. The upstream relies on the GitHub Pages default mapping.
- **No issue history** — the briefing reports zero open or closed
  issues fetched at this commit, so this guide cannot reference real
  issue numbers. Operators should track new issues at
  `https://github.com/resume/resume.github.com/issues` going forward.

### Testing strategy

The repository commits no test suite. The functional contract is small
enough to manually verify with a handful of usernames. A reasonable
manual test plan:

- [ ] `?` (no username) → landing page renders.
- [ ] `?<known-active-user>` → résumé renders with all five blocks.
- [ ] `?<known-inactive-user>` → résumé renders with empty languages and *Inactive* status label.
- [ ] `?<user-without-star>` → opt-out screen renders.
- [ ] `?<nonexistent>` → not-found screen renders.
- [ ] `?<known-org>` → organisation résumé renders with the org template.
- [ ] Print preview from any rendered résumé → clean print layout.

For automated coverage on a fork, a Playwright or Cypress smoke test
that exercises each branch is sufficient.

### Contributing guide

The upstream has been quiet for years. If you intend to contribute back:

1. Open an issue first describing the change. Without an issue, drive-by
   PRs may not be reviewed.
2. Keep changes minimal. The project is intentionally tiny.
3. Maintain ES5 compatibility unless you also bring a build step.
4. Test by serving `rackup config.ru` and exercising a handful of
   real usernames as listed above.
5. Update `README.markdown` if a contributor-visible behaviour changes.

For private forks, treat the upstream as a starting point and feel free
to modernise (ES modules, fetch instead of jQuery AJAX, gtag.js, etc.).

## 9. Integration Points

This section enumerates every external service the project touches and
how to swap or extend each.

### Outbound integrations

| Target | Endpoint | Auth | Purpose |
|--------|----------|------|---------|
| GitHub REST API — users | `https://api.github.com/users/<user>` | None (public) | Fetch profile. |
| GitHub REST API — repos | `https://api.github.com/users/<user>/repos` | None (public) | Fetch non-fork repositories for the projects block and language histogram. |
| GitHub REST API — orgs | `https://api.github.com/users/<user>/orgs` | None (public) | Fetch organisation memberships. |
| GitHub REST API — starred | `https://api.github.com/users/<user>/starred` | None (public) | Opt-in check. |
| GitHub Search API — issues | `https://api.github.com/search/issues?q=type:pr+is:merged+author:<user>` | None (public) | Fetch merged PRs. |
| Gravatar | `https://secure.gravatar.com/avatar/<hash>` | None | Used for organisation avatars (extracted from the `data.avatar_url` regex match). |
| GitHub Emoji CDN | `https://assets-cdn.github.com/images/icons/emoji/<name>.png` | None | Replaces `:emoji:` shortcodes in repo descriptions. |
| Google Analytics | `https://ssl.google-analytics.com/ga.js` or `http://www.google-analytics.com/ga.js` | None (key is `UA-21222559-1`) | Page-view tracking. |
| External CDNs | jQuery, Mustache | None | Loaded by the HTML templates; exact CDN inferred from rendered HTML. |

### Replacing GitHub Analytics with GA4

To migrate to GA4, replace the entire trailing `if (trackerId)` block in
`js/githubresume.js` with a `gtag.js` snippet:

```html
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX');
</script>
```

Or remove analytics entirely by deleting the block and the `trackerId`
variable.

### Replacing the GitHub data source

The four `github_user_*` functions are the single integration boundary.
A fork that wants to render résumés from a different code-hosting
platform (GitLab, Gitea, etc.) needs to:

1. Reimplement each `github_user_*` function against the target API.
2. Reshape the response to match the field names the Mustache templates
   expect (see §3 for the variable list).
3. Decide what the equivalent of "starring the repo" is on the target
   platform — without it, every fork has no opt-in mechanism.

### Adding a server-side proxy

If you need authenticated rate limits without exposing a token, add a
small server-side proxy that:

1. Accepts only the GET endpoints listed in the table above.
2. Forwards them to `api.github.com` with an `Authorization: token <pat>`
   header read from a server-side environment variable.
3. Caches responses for ~5 minutes.
4. Returns CORS headers permitting your origin only.

The front end then only needs to change the URL prefix from
`https://api.github.com` to your proxy's domain. The rest of
`js/githubresume.js` is unaffected.

### Hosting integrations

The repository works on any static host. The most common production
configurations are:

| Host | Configuration | Notes |
|------|---------------|-------|
| GitHub Pages | Repo → Settings → Pages → Source = `master` branch | Matches upstream. |
| Netlify | `netlify.toml` not required; default static deploy works | Add a `_redirects` file if you want clean URLs. |
| Vercel | `vercel.json` not required | Add `cleanUrls: true` for `/?user` to render `index.html` automatically. |
| Cloudflare Pages | Default static build | Effectively zero config. |
| nginx | `try_files $uri $uri/ /index.html;` | Required so `/?user` correctly serves `index.html`. |
| Apache | `DirectoryIndex index.html` | Default behaviour suffices for most installs. |

> [!NOTE]
> The query-string-as-input pattern means the path always resolves to
> `/`. There are no nested routes to map. Single-page-app fallback
> rules (`try_files`, `cleanUrls`) are not strictly required, but they
> protect against accidentally serving a 404 if a user trims the
> trailing `/` from the URL.

### Browser integrations

The project depends on the browser's native APIs:

- `window.location.search` for the query string.
- `window.print()` for the print button.
- `document.title` for the dynamic title.
- `console.log` for the silent error sink.
- `Date` for year computation.

No `navigator` features (geolocation, clipboard, service workers) are
used. The project will run in any browser with ES5 support.

---

_Generated April 2026 from resume/resume.github.com at f599075. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
