Open Budget: Sacramento transforms City budget data into clear, accessible, and civic-minded visualizations so residents can understand where money comes from, where it goes, and how it changes over time.
- Background
- Install
- Usage
- CI/CD
- Source Tree
- Total Features
- Tech Stack
- List of Dependencies
- Outstanding Concerns / Issues / TODOs
- Contributing
- Maintainers
- License
Open Budget: Sacramento is a production civic-tech website for fiscal transparency. The project combines static pages, historical budget visualizations, and a modern React comparison workflow. It is maintained by community contributors affiliated with Open Sacramento.
Core goals:
- Make public budget data easier to understand.
- Support both high-level and detailed exploration.
- Keep the site lightweight, testable, and contributor-friendly.
- Node.js 22.x (matches Docker + CI)
- npm
- Optional: Docker
- Optional (data scripts): Python 3 + pandas
# from repository root
cd _src
npm audit fix
npm ci --include=dev
npm run build-css
npm run serveSite will be available at http://localhost:8011.
# from repository root
docker compose up --buildThis launches only the website container at http://localhost:8011.
Optional Docker test targets:
# full Docker test target (lint + unit coverage + a11y + e2e)
docker compose --profile test build test
# parallel verification target
docker compose --profile test build test-parallelFrom _src/:
npm audit fix # resolves supply-side security issues introduced by NPM packages
npm run serve # build TypeScript assets + CSS, then start the Eleventy dev server
npm run watch # watch legacy TypeScript assets and the compare bundle
npm run build:legacy # emit legacy/browser TypeScript assets and patch browser-incompatible prologue lines
npm run build:compare # build the React compare bundle
npm run build:assets # build all JavaScript/TypeScript assets
npm run build # alias for build:assets
npm run build-css # compile Sass
npm run site:build # build the Eleventy site with the TypeScript config
npm run site:serve # serve the Eleventy site with the TypeScript config
npm run docs:jsdoc:autofix # insert missing JSDoc in active TS/JS files and regenerate docs
npm run docs:jsdoc:check # fail if active TS/JS files are missing JSDoc comments
npm run docs:jsdoc # generate Markdown JSDoc inventory in _src/docs/jsdoc
node --import tsx scripts/add-missing-jsdoc.ts # add missing JSDoc blocks in active first-party TS/JS files
npm run typecheck # TypeScript program check for the whole project
npm run lint # ESLint with Google TypeScript style rules
npm run test # lint + typecheck + unit + a11y + e2e
npm run test:i18n:smoke # site + compare i18n smoke tests
npm run test:e2e # preflight + e2e against local server
npm run test:docker # Docker-optimized full suite (coverage + a11y + e2e)
npm run verify:all:parallel # run lint/tests/e2e in parallel (used by Docker verify-parallel target)
npm run perf:report # compare bundle size budgets
npm run bench # benchmark suite
npm run probe:memory # RSS memory probe for serve command (writes JSON report)
npm run probe:memory:csv # convert serve JSON memory report to CSV
npm run probe:memory:watch # RSS memory probe for webpack watch command (writes JSON report)
npm run probe:memory:watch:csv # convert watch JSON memory report to CSVESLint is on flat config via _src/eslint.config.ts. TypeScript linting follows the Google TypeScript style guide through eslint-config-google plus @typescript-eslint.
For performance baselines and guardrails, see PERFORMANCE.md and _src/PERFORMANCE.md.
Memory profiling outputs:
- Serve probe JSON report:
_src/test/memory-serve-report.json - Serve probe CSV report:
_src/test/memory-serve-report.csv - Watch probe JSON report:
_src/test/memory-watch-report.json - Watch probe CSV report:
_src/test/memory-watch-report.csv
- CI workflow (
.github/workflows/ci.yml) validates:- npm checks (lint, typecheck, i18n unit smoke, unit coverage in CI, a11y, perf, bench, JSDoc coverage check, JSDoc docs generation, Eleventy build)
- dependency installs with Puppeteer browser download disabled for non-E2E jobs (
PUPPETEER_SKIP_DOWNLOAD=true npm ci --include=dev) - built-site i18n sanity checks (
build/js/i18n-site.jsand localized markup wiring in generated HTML) - explicit Chromium browser install for E2E (
npx puppeteer browsers install chrome) after Linux shared-library provisioning - E2E with a prebuilt frontend bundle via
npm run test:e2e:nobuild - legacy Overview/Detail smoke coverage in both
en-USandes-419 - Docker targets:
docker compose build site(runtime/default website image)docker build --target test .(full Docker-optimized test suite)docker build --target verify-parallel .(parallel verification target)
- Deploy workflow (
.github/workflows/deploy.yml) runs i18n smoke tests, verifies JSDoc coverage, generates JSDoc docs, builds static files from_src, validates i18n assets in output, and publishes to GitHub Pages.
openbudgetsac.org/
├── .github/
│ └── workflows/
│ ├── ci.yml # continuous integration workflow
│ └── deploy.yml # GitHub Pages deployment workflow
├── PERFORMANCE.md # repository-level performance summary + guardrails
├── _src/ # primary development workspace
│ ├── __tests__/ # e2e tests
│ ├── bench/ # benchmark harness
│ ├── css/ # Sass + legacy vendor CSS assets
│ ├── data/ # compare/flow/tree data + Python scripts
│ ├── docs/
│ │ └── jsdoc/ # generated JSDoc Markdown index + per-file docs
│ ├── generated/ # compiled browser-ready TypeScript output
│ ├── images/ # static images
│ ├── js/
│ │ ├── compare/ # React comparison app
│ │ │ └── i18n.ts # compare-page i18n catalog + language resolution
│ │ ├── i18n-site.ts # site-wide i18n runtime (legacy + modern templates)
│ │ └── old/ # legacy OpenSpending scripts
│ ├── partials/ # shared Pug partials/layouts
│ ├── scripts/
│ │ ├── add-missing-jsdoc.ts # inserts generated missing JSDoc in active first-party TS/JS files
│ │ ├── generate-jsdoc-docs.ts # JSDoc Markdown generator (npm run docs:jsdoc)
│ │ └── strip-cjs-prologue.ts # removes browser-breaking CommonJS prologue lines from generated assets
│ ├── templates/ # legacy template pages
│ ├── test/ # test utilities (preflight, static server, perf, RSS memory probe + CSV conversion)
│ │ ├── memory-probe.ts # process-tree RSS sampler for launch commands (JSON report)
│ │ └── memory-report-to-csv.ts # converts memory probe JSON timeline into CSV
│ ├── vendor/ # pinned browser runtime assets (d3, jquery-migrate)
│ ├── eslint.config.ts # ESLint v10 flat configuration
│ ├── jest.config.ts # Jest flat configurate
│ ├── package.json # NPM scripts
│ ├── README.md # general repository documentation
│ ├── tsconfig.json # TS flat configuration
│ ├── webpack.config.ts # Webpack flat configuration
│ └── PERFORMANCE.md # benchmarking repository documentation
├── _treemap/ # treemap data processing utilities
├── Dockerfile # container build and test targets
├── docker-compose.yml # local Docker orchestration
├── civic.json # civic-tech documentation
└── LICENSE.txt # MIT License text
- Public-facing civic information pages (
what-we-do,who-we-are,news,contact,discuss,feedback, and guides). - Budget process and visualization landing pages.
- Legacy/historical budget pages covering multiple fiscal cycles.
- Overview flow visualization for single-year, source-to-use budget movement.
- Detail treemap/breakdown pages for department/fund/category drill-down.
- Comparison React app for side-by-side year comparisons.
- Historical visualization pages retained for continuity and archive value.
- Dual budget-year selection with intelligent defaults.
- Change display modes: percentage and dollars.
- Built-in i18n for American English (
en-US) and Latin American Spanish (es-419). - Breakdown tabs:
- Spending by Department
- Spending by Category
- Revenue by Department
- Revenue by Category
- Accessible chart/table rendering and runtime a11y checks in development.
- Constrained-mode behavior for small screens/low-bandwidth/low-memory devices.
- Site-wide and compare-page localization support for American English (
en-US) and Latin American Spanish (es-419). - Resolution order for locale selection:
langquery parameter, local storage (ob.locale),<html lang>, then browser language. - Runtime propagation of selected locale to internal links so page transitions keep language state.
data-i18n,data-i18n-html,data-i18n-title, anddata-i18n-aria-labelsupport across navigation, compare UI text, and legacy flow/tree labels.- Dynamic data labels in Comparison and Detail views are translated through the site i18n runtime helper for
es-419. - Dedicated unit tests for compare and site runtime locale behavior (
i18n.test.ts,siteI18n.test.ts).
- Production-mode webpack builds for compare bundle.
- Automated bundle-size budget reporting (
npm run perf:report). - RSS memory probe tooling for startup/watch diagnostics (
npm run probe:memory,npm run probe:memory:watch). - CSV export utility for memory timelines (
npm run probe:memory:csv,npm run probe:memory:watch:csv). - Linting via ESLint,
@typescript-eslint, and the Google TypeScript style guide. - Unit + accessibility tests via Jest.
- E2E verification via Puppeteer with Linux dependency preflight checks.
- Benchmarks via
bench-node. - CI pipeline for lint/test/build/perf/bench and Docker target validation.
- GitHub Pages deployment pipeline.
- Python CSV splitting utility for flow inputs.
- Python budget-asset generation for compare/tree datasets.
- Treemap data tooling under
_treemap/.
| Tool | Version | Badge | Link |
|---|---|---|---|
| Node.js | 22.x |
https://nodejs.org/ | |
| npm | bundled with Node 22 | https://www.npmjs.com/ | |
| Eleventy | ^3.1.5 |
https://www.11ty.dev/ | |
| Pug | ^3.0.4 |
https://pugjs.org/ | |
| Webpack | ^5.106.1 |
https://webpack.js.org/ | |
| Dart Sass | ^1.25.0 |
https://sass-lang.com/ |
| Library | Version | Badge | Link |
|---|---|---|---|
| React | ^19.2.5 |
https://react.dev/ | |
| React DOM | ^19.2.5 |
https://react.dev/ | |
| Axios | ^1.15.0 |
https://axios-http.com/ | |
| Chart.js | ^4.5.1 |
https://www.chartjs.org/ | |
| react-chartjs-2 | ^5.3.1 |
https://react-chartjs-2.js.org/ | |
| react-bootstrap | ^2.10.10 |
https://react-bootstrap.github.io/ | |
| react-select | ^5.10.2 |
https://react-select.com/home | |
| d3-array | ^3.2.4 |
https://d3js.org/ | |
| d3-collection | ^1.0.7 |
https://d3js.org/ | |
| d3-color | ^3.1.0 |
https://d3js.org/ | |
| d3-format | ^3.1.2 |
https://d3js.org/ | |
| d3-interpolate | ^3.0.1 |
https://d3js.org/ | |
| d3-scale-chromatic | ^3.1.0 |
https://d3js.org/ |
| Tool | Version | Badge | Link |
|---|---|---|---|
| TypeScript | ^6.0.2 |
https://www.typescriptlang.org/ | |
| Jest | ^30.3.0 |
https://jestjs.io/ | |
| Testing Library | ^16.3.2 |
https://testing-library.com/ | |
| jest-axe | ^10.0.0 |
https://github.com/nickcolley/jest-axe | |
| Puppeteer | ^24.40.0 |
https://pptr.dev/ | |
| ESLint | ^10.2.0 |
https://eslint.org/ | |
| eslint-config-google | ^0.14.0 |
https://github.com/google/eslint-config-google | |
| tsx | ^4.21.0 |
https://github.com/privatenumber/tsx | |
| start-server-and-test | ^3.0.2 |
https://github.com/bahmutov/start-server-and-test | |
| GitHub Actions | workflow | https://docs.github.com/actions |
All dependencies required for active development are listed below.
axios@^1.15.0chart.js@^4.5.1core-js@^3.49.0d3-array@^3.2.4d3-collection@^1.0.7d3-color@^3.1.0d3-format@^3.1.2d3-interpolate@^3.0.1d3-scale-chromatic@^3.1.0dart-sass@^1.25.0pug@^3.0.4react@^19.2.5react-bootstrap@^2.10.10react-chartjs-2@^5.3.1react-dom@^19.2.5react-select@^5.10.2react-spinkit@^3.0.0
@11ty/eleventy@^3.1.5@11ty/eleventy-plugin-pug@^1.0.0@axe-core/react@^4.11.1@babel/cli@^7.28.6@babel/core@^7.29.0@babel/preset-env@^7.29.2@babel/preset-react@^7.28.5@babel/preset-typescript@^7.28.5@babel/register@^7.28.6@eslint-react/eslint-plugin@^4.2.3@eslint/js@^10.0.1@testing-library/jest-dom@^6.9.1@testing-library/react@^16.3.2@types/jest@^30.0.0@types/jquery@^4.0.0@types/react-dom@^19.2.3@typescript-eslint/eslint-plugin@^8.58.2@typescript-eslint/parser@^8.58.2babel-jest@^30.3.0babel-loader@^10.1.1bench-node@^0.14.0css-loader@^7.1.4eslint@^10.2.0eslint-config-google@^0.14.0eslint-plugin-jest@^29.15.2[email protected]globals@^17.5.0jest@^30.3.0jest-axe@^10.0.0jest-environment-jsdom@^30.3.0puppeteer@^24.40.0start-server-and-test@^3.0.2style-loader@^4.0.0tsx@^4.21.0typescript@^6.0.2webpack@^5.106.1webpack-cli@^7.0.2
_src/data/split_csv.pyand_src/data/generate_budget_assets.pyrequire:pandas(also listed in_treemap/requirements.txt)
The E2E preflight checks and Docker test targets rely on shared libraries including:
libasound2,libatk1.0-0,libatk-bridge2.0-0,libatspi2.0-0,libcairo2,libcups2,libdbus-1-3libdrm2,libgbm1,libgtk-3-0,libnspr4,libnss3libpango-1.0-0,libx11-6,libx11-xcb1,libxcb1,libxext6libxcomposite1,libxdamage1,libxfixes3,libxkbcommon0,libxrandr2,libxss1ca-certificates,fonts-liberation,xdg-utils
Authoritative locations:
No first-party TODO/FIXME markers are found in active codebase. However, the following items are known technical concerns to track:
| Concern | Context | Location |
|---|---|---|
| E2E tests require Linux Chromium shared libraries | npm run test:e2e will fail on hosts missing required runtime libs until dependencies are installed. |
_src/test/e2e-preflight.ts |
| Compare bundle remains above Webpack’s default warning threshold | Performance has improved significantly, but bundle size still exceeds 244 KiB default warning level. | PERFORMANCE.md |
| Large source images still impact strict 3G targets | Optional optimization work remains for modern image formats and responsive variants. | PERFORMANCE.md |
| Legacy OpenSpending/jQuery treemap path is still in production code | Historical widget path increases maintenance burden and modernization effort. | _src/js/old/treemap.ts |
| Historical page copy contains dated “pending” notices from 2016 | Content is factually historical, but appears stale/confusing if read as current status. | _src/2016-17-adjusted-budget-flow.jade:10, _src/2016-17-adjusted-budget-tree.jade:10 |
Contributions are welcome, but please consult with the Open Sacramento community first.
- For starter tasks, browse issues labeled
help wanted. - For larger architectural changes, open an issue and discuss approach first.
- Keep changes simple, focused, tested, and documented.
Typical workflow:
- Fork the repository.
- Create a feature branch.
- Work in
_src/. - Run lint/tests locally.
- Open a pull request against
main.
Useful commands from _src/:
npm audit fix
npm run lint
npm run test:unit:coverage
npm run test:a11y
npm run test:e2e
npm run perf:report