Hybrid static and streaming experience for CEQA and NEPA practitioners. The site pairs an Eleventy-rendered marketing surface with a TypeScript content pipeline, Express SSE endpoints, and progressive enhancement for live news and publication feeds.
/news and /publications upgrade to an SSE-driven terminal UI when JavaScript is available.server/ exposes /api/news and /api/pubs, orchestrating source fan-out, ranking, caching, and streaming.lib/ and power both the server and client.public/components and public/assets/scripts manage the terminal UI, JSON-LD appenders, citation utilities, and the user library./news/tag/ai-governance, /publications/tag/llm) pre-rendered with the same ranking pipeline./library page backed by localStorage with JSON/CSV export utilities..
├── .eleventy.js # Eleventy build configuration
├── components/ # TypeScript sources for browser modules
├── build/ # Generated by TypeScript compiler (gitignored)
├── lib/ # Shared TypeScript content model + fetchers
├── public/ # Client assets (terminal styles, scripts)
├── server/ # Express server (SSE endpoints + static hosting)
├── news.njk / publications.njk / library.njk
├── news/tag.11ty.js # Server-rendered tag pages
├── publications/tag.11ty.js # Server-rendered tag pages
├── components/TerminalFeed.ts → public/components/TerminalFeed.js
└── tests/ # Node test definitions
Copy .env.example to .env and define the variables you need. Only one is required for the live fan-out:
CROSSREF_MAILTO=contact@ceqa.ai
This value is forwarded to Crossref as part of the User-Agent per their API policy. No secrets are exposed to the client.
The default fan-out pulls from:
lib/sources/rss.ts)Add or tune sources via lib/sources/ and capture source-specific transforms inside lib/content/normalize.ts.
| Endpoint | Description | Parameters |
|---|---|---|
/api/news |
Streams curated news, policy, and case updates via Server-Sent Events (SSE) | query, since, tags, type, source |
/api/pubs |
Streams ranked publications via SSE | query, since, tags, type, source |
Each endpoint:
status, item, done, and error events.lib/content/hash.ts).lib/content/rank.ts).lib/content/cache.ts).role="log" with aria-live="polite".public/assets/scripts/seo-jsonld.js appends up to five streamed entries progressively.| Command | Purpose |
|---|---|
npm run dev |
Watch TypeScript + Eleventy rebuilds and run Express on http://localhost:3000 |
npm run build |
Compile TypeScript (server + components) and build Eleventy |
npm start |
Serve the compiled site + SSE endpoints (after npm run build) |
npm test |
Compile server TypeScript and run Node tests |
npm run build:components |
Compile browser modules (TerminalFeed) |
npm run build:ts |
Compile Node/Express TypeScript |
The Express server (server/index.ts) powers both /api/news and /api/pubs. npm run dev already starts it via Nodemon. For production, run npm run build followed by npm start.
Dockerfile and render.yaml build the site (npm ci && npm run build) and boot the Express server (npm start) on port 3000.CEQA_API_BASE (build time, optional): absolute origin for the streaming API when the static site is hosted elsewhere (e.g., https://stream.ceqa.ai). Leave unset when the same Node app serves both HTML and API.ALLOWED_ORIGINS (runtime, optional): comma-separated list of origins allowed by CORS when the API is accessed cross-origin (e.g., https://ceqa.ai,https://www.ceqa.ai).npm cinpm run build (generates _site + compiled server under build/)npm start (served with compression + SSE endpoints)/health returns { ok: true } for uptime probes.CEQA_API_BASE during the Eleventy build so the client-side code calls the remote /api/news and /api/pubs.npm test)
lib/content/hash.ts)/api/news emits status, item, and done events (mocked fan-out)npm run dev, open /news, toggle to Live view, confirm streaming terminal updates and cards./library./news/tag/ai-governance and /publications/tag/llm to verify pre-rendered lists and actions.lib/content/rank.ts.lib/sources/index.ts to add/remove source fan-outs.docs/news-publications.md.