Turn your members' ten-minute logs into a newsletter, social posts, and branded posters.
Members file a short periodic log of what happened → the pipeline compiles a period's public logs into publication drafts (newsletter, social post, brief) → a collateral maker turns the highlights into HTML-first posters in four social formats → one approver signs off, and it ships. Runs two ways: local (a zero-dependency node server plus git, working in 60 seconds) or team (Firebase auth, Firestore, and a hosted dashboard). AI drafting is bring-your-own-key: Anthropic, OpenAI, Gemini, or a local Ollama.
Website · Live demo · Component catalog · The method · Data model · Team mode setup · AI providers
The demo content ships as Orbital Collective, a fictional community lab, so every screen is populated without borrowing anyone's brand.
Every capture above was taken from the demo at docs/demo/, running on the fictional seed data. Screenshots do not regenerate automatically: after changing the seed data, the starter board, or the sample newsletter, re-run node docs/build-site.mjs and recapture the affected screenshots by hand from the regenerated demo pages.
- Log — each member files a short periodic log (weekly or monthly, per your config): highlights, progress, people, the thing they learned, what is coming up, and one line for the public. Five to fifteen minutes, filed as-is, never rewritten by the pipeline.
visibility: internalnever leaves the repo. - Compile — the pipeline assembles a month's public logs into
compilations/<YYYY-MM>/compile-input.md(weekly periods map to months by the ISO-8601 Thursday rule). Generated, never hand-edited: corrections go into the logs, then you regenerate. - Draft — an AI pass (your key, your provider — or a local Ollama) turns the compile input into
newsletter.md,social.md, andbrief.md, following prompts/compile-period.md: no invented facts, unknowns marked[TBD], every exclusion counted innotes.md. - Dress —
collateral/extract.mjssamples your organization's website — colors, fonts, logo — intocollateral/brand/tokens.css. The poster engine incollateral/shared/ships zero brand values; the skin is the only brand surface. - Author — write posters in
collateral/<campaign>/index.htmlfrom a fixed component vocabulary, in four formats: 4:5, 1:1, 9:16, 16:9.collateral/build.mjsinlines everything into one standalone HTML per campaign; PNG/PDF export is one optional dependency away. - Approve — one named approver reads
notes.mdand signs off. Nothing publishes before that; the rhythm is the product.
your-org.com members' logs
| logs/2026/2026-W29-jordan-lee.md
| node collateral/extract.mjs |
v | compile (UI, API, or cli/compile.mjs)
collateral/brand/tokens.css v
(the brand as custom properties) compilations/2026-07/compile-input.md
| |
| | AI draft pass — prompts/compile-period.md
| v
| newsletter.md · social.md · brief.md · notes.md
| |
+------------------+---------------------+
v
collateral/<campaign>/index.html posters from the component vocabulary
|
| node collateral/build.mjs <campaign>
v
<campaign>-standalone.html one file — file:// · email · PNG/PDF export
Requirements: Node >= 20. No npm install — there are no dependencies. (The one exception: PNG/PDF export needs npm install playwright && npx playwright install chromium, which is why playwright appears in package.json as an optional dependency and nowhere else.)
Starting fresh? Click Use this template above to spin up your own copy — your logs and campaigns stay in your repo, and the starter campaign comes along as a live reference.
# (update the clone URL after the first publish of this repo)
git clone https://github.com/fritzhand/pressroom && cd pressroom
cp pressroom.config.example.json pressroom.config.json # set your org name, cadence, sections
node cli/serve.mjs # -> http://localhost:8123Open the UI, file a log, compile the month — then build the branded, email-safe newsletter from the Compile tab (preview and download in place), or from the CLI: node newsletter/build.mjs <month> (--sample renders a complete fictional issue with no setup at all). Git is the collaboration layer: each member clones, runs the server locally, commits their logs, pushes. No hosted backend, no accounts.
For organizations that want sign-in, a hosted dashboard, and logs in a database instead of files:
npm install -g firebase-tools
# then follow docs/firebase-setup.md: create a Firebase project, enable Google
# auth, create Firestore, copy the two example config files, bootstrap the
# first admin doc by hand, and deploy hosting.The SPA in app/ is backend-agnostic — the same app runs against the local REST API or Firebase, and it picks the backend automatically at load. Full steps: docs/firebase-setup.md.
collateral/extract.mjs reads your site's server-rendered HTML and CSS to seed the brand skin. Every signal has a fallback, and the extractor ends with a scorecard telling you what it found and what it missed. The short version:
| Signal | Best case | Fallback |
|---|---|---|
| Colors | CSS custom properties with brand-ish names; theme-color meta |
most-used hex values across the stylesheets |
| Fonts | Google Fonts <link> or @font-face with woff2 |
system font stack + a TODO to hand-download |
| Logo | <img> with "logo" in src/alt/class, SVG preferred |
apple-touch-icon, then og:image |
| Meta | og:site_name, theme-color, favicon |
the domain name |
JS-rendered SPAs are the one hard case — if the CSS never reaches the wire as CSS, there is little to sample. The full story, signal by signal, including how to fix each gap by hand: docs/website-requirements.md.
pressroom/
├── cli/
│ ├── serve.mjs # zero-dependency local server: UI + REST API on :8123
│ ├── compile.mjs # assemble a month's public logs -> compile-input.md (--draft to AI-draft)
│ └── sync.mjs # team mode: pull Firestore logs to files, push drafted outputs up
├── app/
│ ├── index.html # the SPA: log form, compile, campaigns, admin
│ ├── backend.js # picks the Firebase or local REST adapter at load
│ ├── backends/ # local.js (REST) and firebase.js (Auth + Firestore)
│ └── lib/providers.js # BYO-key AI drafting: anthropic | openai | gemini | ollama
├── newsletter/
│ ├── render.mjs # markdown -> email-safe branded HTML; runs in node and the browser
│ ├── build.mjs # compilations/<month>/newsletter.md -> newsletter.html (--sample for a demo issue)
│ └── sample.md # a complete fictional Orbital Collective issue
├── logs/ # member logs — one file per person per period, filed as-is
├── compilations/ # per-month compile inputs + drafted outputs
├── knowledge-base/ # verified org facts the compile step may cite
├── collateral/
│ ├── shared/ # brand-neutral poster engine — 4 formats, never edited per org
│ ├── brand/ # the skin: tokens.css + fonts + assets (the only brand surface)
│ ├── starter/ # kitchen-sink campaign — copy to start a new one
│ ├── extract.mjs # sample your website -> collateral/brand/tokens.css
│ ├── build.mjs # inline everything -> one standalone HTML per campaign
│ └── export.mjs # PNG/PDF export (the one optional dependency: playwright)
├── docs/ # method, components, data model, setup guides — also the GitHub Pages site
│ ├── index.html # the landing page (self-contained, hand-authored)
│ ├── build-site.mjs # regenerates docs/demo/ from app + brand + starter + sample
│ └── demo/ # GENERATED, committed anyway: the in-browser demo, board.html, newsletter.html
├── prompts/ # the AI judgment passes: compile, ingest, refine-skin
├── pressroom.config.example.json # org name, cadence, sections — copy to pressroom.config.json
└── CLAUDE.md # contracts for AI coding agents working in this repo
Generated *-standalone.html, png/, *.pdf, and compilations/*/newsletter.html files are git-ignored — they are build artifacts. Edit the source, rerun the build. The one exception is docs/demo/: it is generated and committed, because GitHub Pages serves the /docs folder with no build step. To publish the site, enable Pages on the repo (Settings, Pages, deploy from branch, /docs folder); after changing the app, the brand tokens, the starter board, or the sample newsletter, regenerate with node docs/build-site.mjs and commit the result — never edit docs/demo/ by hand.
Two operating modes, one app. LOCAL is a node-stdlib server plus git — no accounts, no cloud, working in a minute. TEAM adds Google sign-in, Firestore, and hosting. app/backend.js tries the Firebase config at load and falls back to the local REST adapter, so the same SPA serves both modes without a build step.
Standalone builds. collateral/build.mjs converts linked CSS to <style> blocks, scripts to inline <script>, and every font and image to a data URI. The build fails loudly if any relative reference survives inlining, so a "standalone" file can never quietly depend on the network. Keep source images sensible (about 1600px, quality about 70).
Email-safe branded newsletter. newsletter/render.mjs turns the saved newsletter.md into a single HTML file that survives real email clients: the brand token values are resolved out of tokens.css and inlined (email clients do not support CSS custom properties), the layout is a table-based single column at 640px with inline styles only, and there are no external references. [TBD] markers render loud amber so a draft cannot quietly pass as final. The renderer is deterministic, dependency-free, and runtime-agnostic — the same module builds the file on disk locally (node newsletter/build.mjs <month>, or the Compile tab's preview + download) and renders client-side in team mode.
Public/internal redaction. A log filed with visibility: internal never enters a compile output — and every exclusion is counted, so the approver can see that redaction happened without seeing what was redacted.
AI drafting and ingestion, bring your own key. Four pluggable providers — Anthropic, OpenAI, Gemini, and a local Ollama for the fully offline path. Keys live in your browser's localStorage or in env vars, never in Firestore and never in git. The ingest prompt turns a pasted braindump into the configured log sections as JSON — never inventing, keeping the author's voice. Details: docs/ai-providers.md.
Brand extraction. Point collateral/extract.mjs at your website and get a tokens.css skin seeded from the real design system, with TODO(spot-check) comments on every judgment call. prompts/refine-skin.md is the guided pass that resolves them with evidence.
Component vocabulary. Posters compose a fixed set of classes — kickers, cards, stat tiles, step lists, CTA blocks, the brand foot — all consuming the same tokens, in four fixed formats (1080x1350, 1080x1080, 1080x1920, 1920x1080). Every poster stays on-brand by construction. Full catalog: docs/components.md.
Print and PNG export. Campaign boards open straight from file:// for review; with the optional playwright install, collateral/export.mjs renders each poster to a correctly named PNG (--scale 2 for retina, --pdf for print).
The .tbd discipline. Any fact you don't actually have is wrapped in <span class="tbd">TBD …</span> in posters and [TBD] in markdown, and renders loud. Drafts can't quietly pass as final; grep for class="tbd" before anything ships.
One-approver publishing rhythm. Every compile ends in a notes.md listing exclusions, unverified figures, and open questions. One named approver reads it and signs off — approval is a ten-minute read, not a committee.
- The extractor parses CSS with regexes, not a real CSS engine. Unusual minification, deep
@importchains, and computed values can slip past it. The output is a scaffold withTODO(spot-check)comments, not a finished skin. - JS-rendered SPAs extract poorly. The extractor fetches HTML and CSS; it does not run a browser. If your site paints entirely from JavaScript, expect sparse results and plan on the manual skin path in docs/website-requirements.md.
- Browser AI calls expose your key to that browser profile. Direct-from-browser provider calls mean the key sits in localStorage where extensions and anyone at the keyboard can reach it. Use a spend-capped key, or use Ollama (no key at all), or run drafting from the CLI with env vars. The threat model, honestly stated: docs/ai-providers.md.
- Firebase bootstrap is manual. The first admin document is created by hand in the Firestore console — deliberately, since the security rules allow only admins to write member docs. One-time, five minutes, documented step by step.
- Brand assets belong to the brand. Logos, fonts, and photos extracted from a website are the organization's property. Running pressroom for that organization (or with permission) is the intended use.
- Licensed fonts must not be deployed to public hosting. Team mode's Firebase Hosting deploys the repo root minus an explicit ignore list (
logs/,compilations/,knowledge-base/,cli/, markdown, service-account keys — andcollateral/brand/fonts/, so font binaries stay local and the hosted boards fall back to system stacks). If your skin uses licensed fonts, keep them incollateral/brand/fonts/only, and never weakenfirebase.json's ignore list. Exported PNGs and PDFs are fine to share anywhere.
MIT — see LICENSE.





