A brutalist-designed web app to save, read, analyze, and share web pages, articles, internal documents, podcasts, and video transcripts in a clean, distraction-free reading mode — supercharged with Google Gemini AI.
ReadNow is a full-stack Express + Vite + React single-page application. Server-side it fetches any URL, extracts the article body with Mozilla's Readability engine, stores everything in a local JSON database, and augments each document with an AI-generated executive analysis (summary, key takeaways, action items, sentiment, and suggested tags). It ships with reader-customization, text-to-speech, highlights and notes, an AI Copilot, workspace-wide RAG search, team digests, collections, exports, enterprise governance controls, audit logs, and outbound webhooks.
- Features
- Tech Stack
- Architecture Overview
- Project Structure
- Getting Started
- Available Scripts
- API Reference
- AI Capabilities
- Data Persistence
- Design System
- Deployment on AI Studio / Cloud Run
- Troubleshooting
- License
- Save any URL — paste a link, ReadNow fetches it, extracts the clean article via
@mozilla/readability, and stores title, author, site name, publish date, full HTML, and plain text. - Distraction-free reader mode — custom typography for headings, blockquotes, code blocks, images, and links with brutalist styling.
- Fully customizable reading settings (
ReaderSettingsModal):- Font family: sans / serif / mono
- Font size
- Line height
- Column width (narrow / normal / wide)
- Theme: Brutal Light, Brutal Dark, Sepia, Cyberpunk
- Bionic reading ("speed-reading") mode
- Auto text-to-speech speech rate
- Quick Save + Ingest (
POST /api/quick-save) — a bookmarklet, Chrome-extension style endpoint, or internal tool for dropping in arbitrary text, audio/podcast transcripts, or video transcripts as documents (no URL required). - Text-to-Speech audio player (
AudioPlayer) using the Web Speech API (speechSynthesis+SpeechSynthesisUtterance), with play/pause/skip and configurable rate.
- Highlights with notes (
HighlightsManager) — highlight any sentence in an article in 4 colors (yellow / green / pink / blue) and attach a private note. - Favorites & Archive — star articles and archive or delete them (delete cascades to highlights and comments).
- Reading progress — track %-read per article to feed streaks and analytics.
- Collections (
TeamCollectionsModal) — organize articles into color-coded, team-shared collections (Engineering & Tech, Market Research, Product Strategy, and custom ones). - Collaborative comments — leave timestamped, author-attributed comments on any article.
- Auto AI analysis on every save — an executive summary, 3–4 key takeaways, action items, sentiment classification (Positive / Neutral / Analytical / Critical), and suggested tags.
- Article AI Copilot (
AiCopilotDrawer) — chat with the article, ask questions, get a quick summary, or translate it into any language (default app state: Marathi). - One-click translation — Gemini translates title, HTML content, and a short summary into your target language and stores it per-article.
- Workspace-wide RAG (
GlobalRAGDrawer) — ask a question across your entire library; the server ranks all articles by relevance, selects the top 5, and returns a cited executive answer with source snippets. - Weekly Team AI Digest (
TeamDigestModal) — synthesize the whole team's reading into a digest title, summary, key insights, and a recommended action; copy as Markdown or broadcast to Slack/Teams. - Graceful fallback — every AI feature falls back to a deterministic local heuristic engine when no
GEMINI_API_KEYis configured, so the app works fully offline.
- Outbound Webhooks (
IntegrationsModal) — register any endpoint; ReadNow firesARTICLE_SAVEDandDIGEST_GENERATEDevents automatically (test ping endpoint included). - Bookmarklet / Quick Save — save a page from your browser with a bookmarklet pointing at the quick-save API.
- Export (
ExportModal) — export any article as Markdown (single text/summary/takeaways/highlights), HTML, copy, or print-to-PDF.
- Enterprise Data Governance (
AuditLogsModal):- DLP / PII masking — when enabled, emails, SSNs, and credit-card numbers are redacted (
[REDACTED_EMAIL],[REDACTED_SSN],[REDACTED_CARD]) before text is sent to AI models. - Zero Data Retention toggle — enforces ephemeral prompts.
- Storage & security profile (AES-256 at rest / in transit).
- DLP / PII masking — when enabled, emails, SSNs, and credit-card numbers are redacted (
- Immutable audit trail — every action (
ARTICLE_SAVED,ARTICLE_DELETED,HIGHLIGHT_ADDED,EXPORT_PERFORMED,AI_RAG_QUERY,DIGEST_GENERATED,SETTINGS_UPDATED) is logged with a timestamp, action, actor, and JSON; exportable as CSV. - Settings API — per-app DLP, retention, and auto-digest schedule persisted to the JSON database.
- Analytics Dashboard (
AnalyticsDashboard) — total articles, articles read, total reading time, time saved by AI summaries, reading-streak days, and a top-categories/domains breakdown.
| Layer | Technology |
|---|---|
| Language | TypeScript (ESM) |
| Frontend | React 19, React Router 7, Vite 6 |
| Backend | Node.js + Express 4 (tsx runtime) |
| Styling | Tailwind CSS 4 (via @tailwindcss/vite), custom brutalist CSS layers |
| AI | @google/genai (Google Gemini 2.5 Flash, JSON-mode responses) |
| Article parsing | @mozilla/readability + jsdom |
| Sanitization | dompurify |
| Icons | lucide-react |
| Motion | motion (Framer Motion) |
| Utilities | clsx, tailwind-merge, date-fns |
| Persistence | Flat-file JSON store (fs), no external database required |
| Hosting target | AI Studio applets / Cloud Run (see Deployment) |
┌──────────────────────────────────────────────────────────┐
│ Client (React SPA, served by Vite middleware or /dist) │
│ App.tsx · Navbar · ReaderView · Modals · Drawers │
│ │ ▲ │
│ │ fetch /api/* │ JSON responses │
│ ▼ │ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Express Server (server.ts, port 3000) │ │
│ │ /api/parse · /api/quick-save │ │
│ │ /api/articles · /api/highlights · ... │ │
│ │ Webhook dispatcher · static file serving │ │
│ └──────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────▼───────────────┐ ┌───────────────┐ │
│ │ server/db.ts │ │ server/ai.ts │ │
│ │ JSON file persistence │ │ Gemini client │ │
│ │ data/readnow_db.json │ │ + local fallback │ │
│ └──────────────────────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
- React posts
{ url }toPOST /api/parse. - The server validates the URL, fetches raw HTML with a Chrome
User-Agentheader, and runs it throughJSDOM+Readabilityto extracttitle,byline,siteName,publishedTime,content,textContent, andexcerpt. - If DLP is enabled in settings, PII is masked on
textContent. - Reading time is computed at ~200 WPM.
generateArticleAnalysis()produces the AI summary, and the article is persisted.- An
ARTICLE_SAVEDaudit log entry is written, andARTICLE_SAVEDwebhooks are dispatched. - The saved article (with AI analysis) is returned to the client.
ReadNow/
├── server.ts # Express app, all REST routes, webhook dispatcher, Vite wiring
├── vite.config.ts # React + Tailwind plugins, '@/'-alias, GEMINI_API_KEY define, HMR gate
├── tsconfig.json # TypeScript config with '@/'-alias → root
├── package.json
├── .env.example # Documented environment variables
├── metadata.json # AI Studio metadata (name, description)
├── index.html
├── dist/ # Production build output (generated)
├── data/
│ └── readnow_db.json # JSON database (generated at runtime)
├── server/
│ ├── db.ts # JSON-file persistence layer + all interfaces/models
│ └── ai.ts # Gemini client + heuristics: analysis, copilot, translation, RAG, digest, DLP
└── src/
├── main.tsx # App entry point
├── App.tsx # Root app, routing, global state, DEFAULT_SETTINGS
├── index.css # Tailwind + brutalist design system + reader themes + print styles
├── types/
│ └── index.ts # Shared client types
├── services/
│ └── api.ts # Fetch wrapper around all endpoints (with localStorage fallback)
└── components/
├── Navbar.tsx # Top navigation
├── ArticleCard.tsx # Library grid card
├── AudioPlayer.tsx # Text-to-speech dock
├── AiCopilotDrawer.tsx # Article chat / summarize / translate
├── GlobalRAGDrawer.tsx # Workspace-wide Q&A with citations
├── HighlightsManager.tsx # Highlight color/note management
├── ReaderSettingsModal.tsx # Font/theme/columns/bionic controls
├── ExportModal.tsx # Markdown / HTML / copy / print export
├── TeamCollectionsModal.tsx # Collections view
├── IntegrationsModal.tsx # Bookmarklet, webhooks, quick save
├── AuditLogsModal.tsx # Enterprise governance + audit trail
├── TeamDigestModal.tsx # Weekly AI team digest
└── AnalyticsDashboard.tsx # KPI & stats dashboard
- Node.js (v18.20+ / v20+ recommended — the server uses the global
fetchAPI)
# 1. Clone the repo and enter the directory
cd ReadNow
# 2. Install dependencies
npm install
# 3. Create your local environment file
cp .env.example .env.localCreate a .env.local file in the project root (it is loaded automatically via dotenv / Vite loadEnv).
| Variable | Required | Description |
|---|---|---|
GEMINI_API_KEY |
Yes * | Google Gemini API key for all AI features. If omitted (or set to MY_GEMINI_API_KEY), ReadNow runs with a built-in heuristic fallback — no AI. In AI Studio it is injected automatically from user Secrets. |
APP_URL |
No | Fully-hosted applet URL (AI Studio injects the Cloud Run service URL). Used for self-referential links and API endpoints. |
Tip: In AI Studio, configure the API key via the Secrets panel in the UI — it supersedes
.env.local.
npm run devThis launches the tsx server which also boots the Vite dev server in-memory (middleware mode).
Open http://localhost:3000 — the host header is 0.0.0.0 and the server serves both the Express API and the Vite HMR client from the same origin.
# 1. Type-check
npm run lint # tsc --noEmit
# 2. Build the frontend bundle
npm run build # vite build → dist/
# 3. Serve with the Express server (production mode, static route)
NODE_ENV=production npm start # node server.ts → http://localhost:3000In production (NODE_ENV=production), server.ts serves the compiled static files from dist/ and falls back to dist/index.html for SPA routing.
| Command | Description |
|---|---|
npm run dev |
Start the full-stack dev server (Express API + in-memory Vite HMR on localhost:3000) |
npm run build |
Build the static client into dist/ |
npm run preview |
Preview the built client with Vite |
npm run clean |
Remove the dist/ output directory |
npm run lint |
TypeScript type-check without emitting files (tsc --noEmit) |
npm start |
Run the production server (requires a build) |
Base URL: http://localhost:3000 (or your APP_URL). All requests are JSON; the body limit is 10 MB.
| Method | Path | Description |
|---|---|---|
POST |
/api/parse |
Fetch and extract an article from a URL, run AI analysis if present, persist, log, webhook. Body: { url } |
POST |
/api/quick-save |
Ingest custom doc / audio text / video transcript or web content. Body: { title, content, url?, mediaType?, collectionId? } |
GET |
/api/articles |
List all articles → { articles } |
GET |
/api/articles/:id |
Get one article → { article } |
PATCH |
/api/articles/:id |
Merge-update article fields (archive, favorite, progress, collection, tags…) → { article } |
DELETE |
/api/articles/:id |
Delete article (cascades highlights/comments), logs audit → { success } |
| Method | Path | Description |
|---|---|---|
POST |
/api/ai/summarize |
(Re)generate the AI analysis for an article. Body: { articleId } |
POST |
/api/ai/ask |
Ask the AI Copilot a question about one article. Body: { articleId, question } |
POST |
/api/ai/workspace-ask |
Workspace-wide RAG Q&A over all articles. Body: { query } → { answer, citations, query } |
POST |
/api/ai/digest |
Generate a weekly team AI digest. → { digest }, dispatches DIGEST_GENERATED webhook |
POST |
/api/ai/translate |
Translate article title/content/summary to a target language. Body: { articleId, targetLanguage } |
| Method | Path | Description |
|---|---|---|
GET |
/api/highlights?articleId= |
List highlights (optionally filtered by article) |
POST |
/api/highlights |
Add highlight. Body: { articleId, text, color?, note? } |
DELETE |
/api/highlights/:id |
Delete a highlight |
GET |
/api/comments?articleId= |
List comments for an article |
POST |
/api/comments |
Add comment. Body: { articleId, author?, text } |
| Method | Path | Description |
|---|---|---|
GET |
/api/collections |
List shared, team collections |
POST |
/api/collections |
Create collection. Body: { name, description?, color? } |
DELETE |
/api/collections/:id |
Delete + unassign articles |
GET |
/api/audit-logs |
List audit trail |
GET |
/api/settings |
Get enterprise settings (dlpEnabled, zeroDataRetention, autoDigestSchedule, retentionDays) |
PATCH |
/api/settings |
Update settings. Body: any subset |
GET |
/api/webhooks |
List webhook configs |
POST |
/api/webhooks |
Create webhook. Body: { name, url, events?, enabled? } |
DELETE |
/api/webhooks/:id |
Delete a webhook |
POST |
/api/webhooks/test |
Send a TEST_PING to a URL. Body: { url } |
| Method | Path | Description |
|---|---|---|
GET |
/api/analytics |
Reading stats: totals, reading time, time saved, streaks, category breakdown |
POST |
/api/export |
Export article. Body: { articleId, format }. When format === 'markdown' returns a .md attachment including summary, takeaways, and highlights; otherwise returns { article, highlights } |
ARTICLE_SAVED— payload:{ id, title, url }DIGEST_GENERATED— payload: the full digestTEST_PING— "Webhook verified" ping sent via/api/webhooks/test
Webhook request body shape: { event, timestamp, payload }.
All AI features live in server/ai.ts and run on the Gemini 2.5 Flash model (@google/genai). When no API key is present, every function degrades to a deterministic, local heuristic that keeps the product functional.
| Feature | Endpoint | Model-mode response |
|---|---|---|
| Auto analysis (summary/takeaways/actionItems/sentiment/tags) | via parse & /api/ai/summarize |
JSON (responseMimeType: "application/json") |
| Article Copilot Q&A | /api/ai/ask |
Markdown text |
| Translation | /api/ai/translate |
JSON (title + HTML content + summary) |
| Workspace-wide RAG | /api/ai/workspace-ask |
Markdown with [Source N] citations |
| Team digest | /api/ai/digest |
JSON (title, summary, topInsights, recommendedAction) |
| DLP PII masking | applied server-side pre-AI | regex redaction (email/SSN/credit-card) |
The workspace RAG uses a token-frequency relevance scoring pass to select the top-5 articles (else the first 3) and feeds up to ~1,500-char snippets per source into the model, returning relevance-scored citations.
There is no external database — everything lives in a single pretty-printed JSON file:
data/readnow_db.json
Created automatically on first request (and reset if it becomes corrupt). The schema (DatabaseSchema in server/db.ts) contains:
articles[]— full saved articles + AI analysis + translationscollections[]— three seeded defaults (Engineering, Research, Strategy)highlights[],comments[]— annotationsauditLogs[]— capped at 500 entriessettings— enterprise governance flagswebhooks[]— one disabled demo Slack webhook pre-seededanalytics— reading streak + last-read date
In scale: swap server/db.ts for a real datastore (the UI already surfaces "PostgreSQL Sync" in its governance panel) while keeping the same interface; the Express routes are storage-agnostic.
ReadNow uses a neo-brutalist visual identity:
- Thick 4px black borders, hard 8px offset shadows, and pure black/white/gray surfaces with flat
#dc2626(red) and#facc15(amber) accents. - Typography: Space Grotesk (body, loaded via
--font-sans), JetBrains Mono for code/mono, Inter fallback. - Signature utilities in
src/index.css:.brutal-border,.brutal-shadow,.brutal-card,.brutal-button,.brutal-input. - Reader themes —
theme-brutal-light,theme-brutal-dark,theme-sepia,theme-cyberpunk. - Headings in the reader are uppercase with black underlines; blockquotes get a red accent; code/pre blocks are black-on-white with red hard shadows; images respect the
@media printclearing (header, nav, audio dock, and buttons hidden on print).
- Push this repo to a Cloud Run service (or link it in AI Studio).
- In AI Studio, set the
GEMINI_API_KEYin the Secrets panel — it is injected for this process automatically at runtime (thevite.config.tsalso inlines it for the client build from the environment). - Vite's
DISABLE_HMRgate keeps the file-watch quiet in the editor environment to prevent flicker during agent/AI edits. - Set
NODE_ENV=productionin the service so it servesdist/statically.
In AI Studio the app runs at the Cloud Run service URL — the applet link for this instance is:
https://ai.studio/apps/c1dbe0d5-a3a4-4f5d-8f64-e8e6bcda90b6
| Problem | Likely cause / fix |
|---|---|
| Articles save without summaries | GEMINI_API_KEY missing → the app uses the heuristic fallback. Set the key in .env.local or AI Studio Secrets and retry. |
| Port already in use | Another process on 3000 — kill it or change PORT in server.ts. |
| A web page fails to parse | The site blocks bots/returns 403 — try a different URL. Readability can also return null for non-article pages (JS-only apps, videos). |
| Webhooks not firing | Ensure the webhook is enabled and its event list includes ARTICLE_SAVED/DIGEST_GENERATED. Use /api/webhooks/test. |
| Data resets unexpectedly | Corrupt data/readnow_db.json is re-initialized automatically — back up the data/ folder for persistence. |
tsc errors |
Run npm run lint to type-check; ensure Node ≥18 for fetch support. |
Private / internal — see package.json and repository settings. All third-party trademarks and service names are the property of their respective owners.