Skip to content

thalida/codecity

Repository files navigation

codecity side-on banner: the floating island skyline with the thalida/codecity label

codecity

codecity turns any git repo into a 3D city. It walks the file tree and git history and builds a world: directories become streets, files become buildings, every commit grows a tree, and every author a firefly.

Try it on any repo (local or remote).

Requirements

  • Docker (Docker Desktop on macOS/Windows; engine + WSL on Windows; docker on Linux)
  • A modern browser with WebGL2 (Chrome, Safari, Firefox, Edge)

Quick start

docker run --rm --init --pull=always \
    -v codecity-cache:/cache \
    -p 8080:8080 \
    ghcr.io/thalida/codecity
  1. Open http://localhost:8080/ to reach the Projects page
  2. Enter a repo URL and pick a branch
  3. Explore your city!

Tips

  • --pull=always keeps you on the latest image; drop it to pin to your cached copy
  • Wipe the cache: docker volume rm codecity-cache
  • Port in use? -p 8081:8080

Advanced setup

Local directories

Local repo support is disabled by default. To enable it, set CODECITY_ALLOW_LOCAL_REPOS=1 and mount the directory read-only into the container at the same absolute path:

docker run --rm --init --pull=always \
    -e CODECITY_ALLOW_LOCAL_REPOS=1 \
    -v "$HOME/Documents/Repos:$HOME/Documents/Repos:ro" \
    -v codecity-cache:/cache \
    -p 8080:8080 \
    ghcr.io/thalida/codecity
  • Use multiple -v flags to mount more than one directory
  • codecity only renders git working trees: git init first to render a non-git directory

.codecityignore

For per-project ignores, drop a .codecityignore at the scan root, one pattern per line:

# Skip anywhere named "fixtures"
fixtures

# Skip a specific path (relative to scan root)
tests/fixtures/large-repo

# Un-ignore a default skip (! prefix overrides ALWAYS_SKIP)
!package-lock.json

Skipped by default

Always ignored, even when tracked (! un-ignores them):

  • VCS: .git, .hg, .svn
  • JS: node_modules, package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lock, bun.lockb, deno.lock
  • Python: .venv, venv, env, __pycache__, poetry.lock, uv.lock, Pipfile.lock
  • Rust: target, .cargo, Cargo.lock
  • Go: Gopkg.lock, go.sum
  • PHP: composer.lock
  • Ruby: Gemfile.lock
  • Elixir: mix.lock
  • CocoaPods: Podfile.lock
  • Nix: flake.lock
  • Framework caches: .next, .nuxt, .svelte-kit
  • Test / coverage: .pytest_cache, .mypy_cache, .ruff_cache, .tox, .coverage, htmlcov
  • IDE / OS: .idea, .vscode, .DS_Store
  • Generated artifacts: sbom.json (CycloneDX / SPDX software bill of materials)
  • Vendored single-file amalgamations: sqlite3.c, miniz.c, lua.c (one giant .c blob inlining a whole library: 100k+ lines that would otherwise render as a single skyscraper distorting every height-based visual)

Reading the city

A large repo rendered with the gem, streets, buildings, and fireflies all visible at once

Buildings: one per file

Skyscrapers close-up showing lit windows, file-extension hues, and the street label below

  • Height: line count (sqrt-interp across the floor range)
  • Width & depth: byte size (log-interp, square footprint)
  • Hue: file extension
  • Saturation: last-modified (recent → vivid)
  • Lightness: last-modified (recent → bright)
  • Windows: lit-pane density, plus a glow that tracks how recently the file was created (newer files glow brighter)
  • Aging: older files get grime streaks and a slight lean
  • Media files (images, video) render an ad-panel face on the front above the door

Streets: one per directory

Top-down view of the street grid with directory labels painted on the asphalt

  • Width tier: descendant count (step function)
  • Length: packed siblings + spacing
  • Label: directory name painted on the asphalt

Trees: one per commit

A repo with thousands of commits rendered as a dense forest overtaking the city

  • Placement: oldest commit closest to the gem, newest at the edges
  • Height: commit age (older = taller)
  • Canopy width: files changed in that commit
  • Color: commits-per-day (solo-day vs busy-day color blend)

Fireflies: one orb per author on each commit

Close-up of low-poly trees with white firefly orbs drifting between them

  • Color: per author. Each committer gets their own hue
  • Scale: that author's total commit count
  • Co-authored commits: Co-authored-by: trailers parsed out of the commit message. Each distinct contributor on a commit gets their own firefly orbiting that tree, in their own color

Gem: the root beacon

The glowing pink gem floating above the root street, lighting the buildings around it

  • Root marker: floats above the root street
  • Click: clears the selection and resets the view

Settings

Tab Section Tweaks
World Camera view angle (elevation + azimuth), always aimed at the root gem
Scene sky color, stars
Island silhouette, materials
Buildings floor + width ranges, per-extension hue map, palette ranges, facade detail, aging, selection-fade
Streets width tiers, spacing, colors, label typography
City footprint the paved apron under the city
Gem sizing, materials
Trees visibility, color (commits-per-day), height (by age), width (by files), outlines
Fireflies visibility, scale range, motion, orbit ring
Effects bloom, selection outline, level-of-detail
Live updates Enabled re-render when files change on disk (off by default)
Poll interval how often to check (min 1s / max 60s)
Appearance Accent highlight color (Amber, Green, Cyan, Blue, Purple, Pink)
Surface background palette (Cool, Neutral, Green, Warm)
Syntax theme file-preview highlighting

How it works

  1. Clone or read: Remote repos clone into a local cache (current tree only); local folders are read in place.
  2. Scan: codecity reads only git-tracked files (git ls-files), honoring .codecityignore and the default skips, and records each file's created and last-modified dates plus each commit's files, authors, and date.
  3. Stream: Packed into one manifest and streamed to the browser as it's computed: a skeleton city renders first as a placeholder, then fills in with the full scan.
  4. Layout: An off-main-thread pass packs the streets so nothing overlaps: directories become streets, files line up as buildings, subdirectories branch off at right angles.
  5. Build: Each building is sized from its file (height = lines, footprint = bytes), one tree per commit (oldest nearest the gem), a firefly per author.
  6. Render: Drawn with three.js (WebGL).

Development

Setup

You need:

git clone https://github.com/thalida/codecity.git
cd codecity
just install-hooks   # one-time: pre-push hooks (lint + prettier + tests)

The pre-push hook runs the full lint + tests before pushing; bypass with git push --no-verify (Docker must be running).

Commands

Command What it does
just dev Vite HMR + API auto-reload at http://<slug>.localhost:<port>/
just test pytest + vitest in containers
just lint ruff, eslint, prettier, and typecheck
just fmt apply Python formatting (ruff)
just gen-types regenerate the frontend wire types from the OpenAPI schema
just build build the local Docker image
just run run the local image like an end user
just screenshots [names] regenerate the README screenshots
just demo-video record the README demo.mp4
just clean tear down this worktree's containers and volumes

Worktrees

  • Each worktree gets its own <slug>.localhost URL, so source-picker recents stay isolated per project in localstorage
  • just dev and just run take a path arg to mount a local repo (just dev ~/Documents/Repos/myproj), which also sets CODECITY_ALLOW_LOCAL_REPOS=1; without it, codecity is git-URL-only

Backend

  • FastAPI on uvicorn, single process by design (the in-memory scan-root trust set in api/security.py can't be split across workers)
  • scan progress streams over Server-Sent Events (GET /api/manifest)
  • API docs at /api/docs (Scalar); raw schema at /api/openapi.json
  • that schema is the source of truth for the generated frontend wire types (just gen-types, drift-guarded by app/src/types/manifest.contract.ts)

Release

just release v0.2.0

just release:

  • verifies you're on a clean main in sync with origin
  • creates an annotated tag and pushes it

Pushing the tag triggers GitHub Actions, which:

  • builds a multi-arch image (linux/amd64 + linux/arm64)
  • pushes to ghcr.io/thalida/codecity with all tag aliases
  • signs with cosign (keyless via OIDC)
  • smoke-tests via /api/health
  • creates a GitHub Release

Verify signatures

cosign verify \
  --certificate-identity-regexp 'https://github.com/thalida/codecity/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/thalida/codecity:v0.2.0

License

AGPL-3.0

About

Visualize any git repo as a 3D city

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors