11 KiB
TrueNews
A responsive news journal generated from pages/*.md. Includes a front page, search, category filters, individual article pages, and a 404 page. Article HTML is generated at build time, so articles work without JavaScript.
Run
Requires Node.js 22 or later.
npm install
npm run dev
Open http://localhost:3000. Changes to pages/ and public/ rebuild the site; refresh your browser to see them. Set PORT to choose a different port. Restart the server after changing files in scripts/.
Write an article
Add a .md file directly inside pages/:
---
title: A new perspective
description: A short introduction for the front page.
date: 2026-09-12
category: World
author: Alex Morgan
url: /world/a-new-perspective/
image: /assets/my-photo.jpg
---
Your opening paragraph.
## The bigger picture
Write with **Markdown**, including lists, links, quotes, and images.
All metadata is optional. url (or slug) sets the custom URL; otherwise my-story.md becomes /my-story/. URLs support letters, numbers, hyphens, underscores, and nested path segments. Duplicate, unsafe, and reserved URLs fail the build. /assets/ and /404/ are reserved. A title falls back to the first Markdown H1, then the filename. Articles sort by date, newest first; the first article is featured. Reading time and a fallback description are calculated automatically.
Put images in public/ and refer to them as /assets/filename.jpg, or use HTTPS image URLs. Articles without an image receive a decorative illustration. Remote images and optional Google Fonts need internet access; system fonts provide a fallback. The included sample articles are illustrative content, not verified reporting. Replace or delete them before publishing.
Build and deploy
npm test
npm run build
Publish dist/ to a static web host that serves directory index.html files. For example, /world/a-new-perspective/ corresponds to dist/world/a-new-perspective/index.html. Configure the host to use 404.html for missing pages; do not use a single-page-app fallback. Adding or updating Markdown requires rebuilding and redeploying. npm start also builds and serves the site locally.
Generate an article with local AI
Start Ollama with llama3.2:3b installed. Start the included local SearXNG search service once (requires Docker and Docker Compose):
npm run search:start
This downloads the SearXNG image on first use and runs it at http://localhost:8888, accessible only from this computer. Allow a few seconds for startup. No search API key is required. A random local secret is created in the git-ignored search/.env; running the setup again preserves it. The container restarts with Docker.
Then give the generator a short prompt:
npm run generate -- "Recent developments in urban renewable energy"
The command searches for sources, downloads up to three readable pages, sends their excerpts to llama3.2:3b, validates its structured response, saves a new pages/*.md file, and rebuilds dist/. It prints the article URL when finished. With npm run dev running, refresh the website to see it. Generation can take several minutes depending on your hardware.
For specific reporting, supply one or more source URLs:
npm run generate -- "Explain how Ollama structured outputs work" \
--source https://docs.ollama.com/capabilities/structured-outputs \
--url /technology/ollama-structured-outputs/
Repeat --source to use multiple pages. Explicit URLs skip search. --url is optional; the default includes a date, title slug, and random suffix to avoid overwriting articles. --no-build saves only the Markdown; the development server also watches for new files.
Configuration:
OLLAMA_HOST: defaults tohttp://127.0.0.1:11434. The generator always usesllama3.2:3b, never the larger installed model.SEARXNG_URL: optional custom SearXNG address. Without a Brave key, the default ishttp://127.0.0.1:8888. JSON output must be enabled undersearch.formats; the included configuration already does this.BRAVE_SEARCH_API_KEY: optional alternative to local SearXNG. Used when a key is provided andSEARXNG_URLis not explicitly set.
Optional settings can be saved in .env (see .env.example); npm run generate loads that file automatically. Environment variables already set in the shell take precedence.
SearXNG searches external engines and returns source URLs. TrueNews downloads the pages, then sends their text to the local model. The model does not need another installation or a larger replacement. External engines can still time out or block requests; the generator reports search failures instead of inventing sources. Be specific about the topic and dates you want covered.
Search service commands:
npm run search:logs # Inspect startup or upstream search errors
npm run search:stop # Stop the local search service
npm run search:start # Start it again
If port 8888 is in use, change the host port in compose.yaml and set SEARXNG_URL accordingly. Source discovery uses the documented SearXNG JSON search API; deployment follows its container installation documentation.
Only successfully downloaded pages are supplied to the model. The generation schema restricts citation IDs to the supplied sources. Every paragraph must reference at least one valid source ID; the program adds the actual source links itself, plus source titles, retrieval dates, available publication dates, generation time, model, and original prompt in Markdown metadata. Articles are requested as 3–4 short paragraphs with schema-enforced character limits. If model output is malformed or contains invalid citations, the generator sends the validation error back to the same small model for one correction attempt. If output is truncated, it starts a fresh request for exactly three shorter paragraphs with a 4,096-token allowance, instead of continuing the unfinished JSON. Complete, validated JSON is accepted even if Ollama reports the token limit. It never guesses replacement citations. If validation still fails, generation stops without creating an article. Missing or unreadable sources also stop generation.
Articles appear on the site automatically after generation and are labeled AI-generated and not independently fact-checked. Valid citations do not guarantee that the model interpreted the source correctly. Read the article and original sources before treating it as verified reporting. Source dates are preserved separately from the article's generation date; sources are not guaranteed to be recent. The reader extracts up to 5,000 characters per page and cannot read paywalls or pages requiring JavaScript.
Generation is a local terminal command, not a public website endpoint. Source text is sent to your configured Ollama server; prompts are sent to the search provider only when source discovery is used. The original prompt is retained in the local Markdown metadata.
The integration uses Ollama's documented chat API and structured outputs.
Browser push notifications
Run npm start (or npm run dev) to serve the notification API and background delivery worker. Visitors see a dismissible invitation; clicking Enable notifications opens the browser permission dialog. The footer lets them turn notifications off or enable them later. Dismissed invitations stay hidden for 30 days. Denied permissions are not requested automatically again.
No Google account, Firebase project, or search API key is needed for notifications. The site uses standard Web Push with automatically generated VAPID keys. The browser chooses its push delivery service.
Settings go in .env and are loaded by the server at startup:
# Local testing:
SITE_URL=http://localhost:3000
# After deployment, replace with your real HTTPS address:
# SITE_URL=https://news.your-domain.com
# Optional contact address used for Web Push authentication:
# VAPID_SUBJECT=mailto:you@your-domain.com
# Optional controls:
# PORT=3000
# PUSH_ENABLED=false
# PUSH_DATA_DIR=.data/push
Restart the server after changing .env. A production SITE_URL must match the browser's public origin exactly, including any nonstandard port. Behind a reverse proxy, forward /api/push/*, /sw.js, and the website to this Node server; serve the public site over HTTPS. The API validates subscription requests against SITE_URL.
Subscriptions, signing keys, delivery retries, and previously published article URLs are stored in .data/push/, outside the public site and ignored by Git. Persist and back up this directory across deployments. Losing signing keys invalidates existing subscriptions. This implementation is intended for one running Node server with persistent disk, not multiple replicas or ephemeral/serverless storage. A static-only deployment of dist/ can display articles but cannot accept subscriptions or send notifications.
After a successful Markdown rebuild, the server queues new article URLs and normally begins delivery within five seconds. Existing articles on first startup and edits to previously seen URLs do not produce notifications. New URLs discovered after an ordinary restart are queued for existing subscribers. Temporary delivery failures retry up to five total attempts; expired subscriptions are removed. Pending notifications expire after 24 hours. Delivery is best effort and depends on the browser, OS settings, and push provider. A process crash at the moment of delivery may repeat a notification; article-specific notification tags reduce duplicates.
Desktop browsers can test on http://localhost:3000; other hosts require HTTPS. For Safari testing, set VAPID_SUBJECT to your contact mailto: address, because Apple's push service rejects a localhost VAPID subject. On iPhone/iPad (iOS/iPadOS 16.4+), add the HTTPS website to the Home Screen and open it there before enabling notifications. A web app manifest and notification icons are included. The service worker opens the article when its notification is clicked and does not cache article pages.
References: browser notification permissions, Apple Web Push support, Web Push library.
Notification verification: npm test covers persistence, queuing, retries, origin checks, and service-worker behavior. For browser tests, start a separate preview with PORT=3100 SITE_URL=http://localhost:3100 PUSH_DATA_DIR=/tmp/truenews-push-tests npm start, then run npm run test:browser. Set CHROMIUM_PATH if needed, or install Playwright Chromium with npx playwright install chromium. Browser tests mock permission and subscription enrollment, exercise the actual subscription API, and verify real service-worker registration; they do not send real device notifications.