Granarium: A Read-Only GitHub Frontend with Dev-Tool Aesthetic
BunReactGitHub APIShikiTypeScript
Granarium: A Read-Only GitHub Frontend with Dev-Tool Aesthetic
Granarium is a hyper-performant, stylish alternative to browsing repos on github.com. It consumes the GitHub REST API, renders repos, files, commits, and pull requests with server-proxied API calls and a Pierre Computer Company–inspired design language.
No backend. No database. Pure Bun + React + GitHub API.
Architecture
┌─────────────────────────────────────────────────────┐
│ Bun Server │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ API Proxy Routes (src/index.ts) │ │
│ │ /api/github/:owner/:repo/* │ │
│ │ → @octokit/rest (GITHUB_TOKEN hidden) │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ Static File Server │ │
│ │ → dist/ (production) or src/ (dev) │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
▲
│
┌─────────────────────────────────────────────────────┐
│ React SPA (src/App.tsx) │
│ ├── RepoHeader (stats, languages, topics) │
│ ├── BranchTagSelector (branches + tags dropdown) │
│ ├── FileTreePanel (@pierre/trees wrapper) │
│ ├── CodeViewer (Shiki lazy highlighter) │
│ ├── InfiniteCommitList (virtual scroll) │
│ ├── PullRequestPage (PR list + detail) │
│ ├── CommitPage (diff + stats) │
│ └── NetworkGraphPage (React Flow DAG) │
└─────────────────────────────────────────────────────┘
URL Routes
| URL Pattern | Page |
|---|---|
/owner/repo |
Repo browser (Code tab) |
/owner/repo/pulls |
PR list with state tabs |
/owner/repo/pull/N |
Single PR + comments |
/owner/repo/commit/SHA |
Commit detail with diff |
/owner/repo/network |
Interactive commit graph |
File Viewer Pipeline
Files are classified by extension and routed through the appropriate viewer:
| Extension | Mode | Viewer |
|---|---|---|
.js, .ts, .py, .go, etc. |
code |
Shiki CodeViewer |
.md, .txt |
markdown |
MarkdownRenderer |
.pdf |
pdf |
iframe (raw.githubusercontent.com) |
.png, .jpg, .svg |
image |
Image viewer |
.csv, .tsv |
table |
TableRenderer (auto-delimiter, 2000 row cap) |
| Other binaries | binary |
“Cannot preview” message |
Key Features
- Code browser — Syntax highlighting via Shiki, file tree with
@pierre/trees(40+ colored icons, virtualized, compact density) - Diff viewer — Side-by-side diffs for commits and PRs with
@pierre/diffs - Pull requests — Infinite scroll PR list with state tabs, slide-in comments panel
- Commit graph — Interactive DAG visualization with React Flow, branch-colored nodes, zoom/pan, dagre left-to-right layout
- Branch/tag selector — Switch branches, browse refs, protected branch badges
- Dark/light theme — OLED black default (
#050505), glass-morphism panels, purple/emerald accents - Caching — IndexedDB with TTL-based expiry per category
- GitHub OAuth — Optional login for higher rate limits (60 → 5,000 req/hr)
Tech Stack
| Layer | Technology |
|---|---|
| Runtime | Bun 1.3 (native TS, HTML imports, built-in bundler) |
| UI | React 19, Tailwind CSS v4 |
| Syntax highlighting | Shiki (lazy singleton, 30+ languages) |
| File tree | @pierre/trees |
| Diffs | @pierre/diffs |
| Commit graph | @xyflow/react (React Flow v12) + @dagrejs/dagre |
| GitHub API | @octokit/rest v22, server-proxied |
| Cache | IndexedDB with TTL |
| Icons | @phosphor-icons/react |
Design System
The design uses OLED black backgrounds with purple (#8b5cf6) and emerald (#10b981) accents, glass-morphism panels with low-opacity borders, and spring-physics transitions (cubic-bezier(0.32, 0.72, 0, 1)). Font sizes are bumped up globally: text-xs → 13px, text-sm → 15px, text-base → 17px, using DM Sans for body text and JetBrains Mono for code.
Key Learnings
- Bun’s HTML imports eliminate build complexity — importing
.tsxdirectly from HTML withBun.serve()removes the need for Vite, esbuild, or webpack in development. - Server-side API proxying keeps tokens safe — the GitHub token lives in
GITHUB_TOKENenv var, proxied through Bun routes, never reaching the client. @pierre/treesprepared input is opaque — the tree input object has internal markers that break when serialized through JSON, so preparation must happen client-side after fetching data.- React Flow handles DAG layout well with dagre — the commit graph uses
@dagrejs/dagrewith left-to-right layout for horizontal git-like lanes, with customCommitNodecomponents showing avatars, SHAs, and branch pills. - Rate limits are the real bottleneck — unauthenticated GitHub API caps at 60 req/hr. Adding a
GITHUB_TOKENjumps to 5,000, which is the difference between a broken and usable browsing experience.