Timepress turns a short event you write into a news article, then shows how a newspaper or magazine from another era and culture would have reported it. Choose the 1890s London press, a 1930s Shanghai daily, a 1980s Chinese national paper or a 1990s tech magazine. Both the writing and the page design change with the edition.
Every page is an openly fictional creative reinterpretation, never an archival article. Your event is the only source of facts.
Write an event in one to three sentences, in English or Chinese, real or imagined.
Choose an edition from the dropdown.
Publish. The backend asks a language model to retell the event in that edition’s editorial voice. The frontend sets the result in that edition’s page design.
Compare. Switch editions to read the same event as another era would have written it. Results are cached for the session, so switching back is instant.
With no model configured, the site runs in a clearly labelled demo mode, so it can be shown without any API key.
Features
Four editions, each with its own editorial voice and page design, two Western and two Chinese.
Cross-language input: write in English or Chinese for any edition. The output is written in the edition’s language: English, Traditional Chinese or Simplified Chinese.
Interface in five languages: English, 日本語, 繁體中文, 简体中文 and Español, chosen from the top-right corner.
Two model API formats, each with official or third-party endpoints: OpenAI-compatible and Anthropic Messages API.
Settings in the browser (owner only): add providers, save keys to the OS keychain, pick a model and reasoning level, and run a connection test. This follows the same flow as ResearchDeck.
Honest by design: visible “creative reinterpretation” labels, sample content marked as such, and a prompt that forbids invented names, quotes, dates or figures.
Seven sample stories, each hand-written for all four editions (28 articles), so demo mode still reads well.
Copy article and Print / Save as PDF.
Accessible and responsive: keyboard-operable controls, live status announcements, correct lang attributes, prefers-reduced-motion support, and a single-column layout on phones.
No images: every page is typeset with HTML, CSS and inline SVG only.
Editions
Edition
Fictional publication
Output language
Look and voice
London · 1890s Broadsheet
The London Chronicle
British English
Ivory paper, double rules, three justified columns and a drop cap. Measured, formal and quietly curious.
Shanghai · 1930s Daily
海上新報
Traditional Chinese
Warm newsprint, a vertical calligraphic masthead and authentic right-to-left vertical text on wide screens (with a toggle). Brisk prose mixing classical and vernacular Chinese.
China · 1980s National Daily
时代通讯
Simplified Chinese
A red masthead and rules, a section box and column rules. Concise, formal, institutional reporting.
Technology · 1990s Magazine
NEXT BYTE
American English
Bold sans-serif type, neon panels, an oversized “01” and a stats sidebar. Punchy and jargon-aware.
Every masthead is an original, fictional name. Real papers served only as research inspiration.
Tech stack
Layer
Technology
Frontend
React 19, TypeScript, Vite; plain CSS with custom properties; no UI framework
Backend
Python 3.12, FastAPI, Pydantic, Uvicorn
Model access
httpx for OpenAI-compatible endpoints; the official anthropic SDK for the Anthropic Messages API
Secrets
OS keychain via keyring (Settings dialog), or backend/.env
Tooling
pnpm (JavaScript), uv (Python), oxlint, pytest
Storage
None: no database, accounts or analytics
Browser (React) ──/api──▶ FastAPI ──▶ OpenAI-compatible API or Anthropic Messages API
▲ │
└── typeset page ◀────┘ validated JSON {headline, deck, section, paragraphs}
In development, Vite proxies /api to the backend, so API keys never reach the browser.
Getting started
Prerequisites
Node.js 22.12+ and pnpm
uv (it installs Python 3.12 for you)
node --version && pnpm --version && uv --version
1. Install
git clone <your-repo-url> timepress
cd timepress
cd backend && uv sync && cd ..
cd frontend && pnpm install && cd ..
2. Configure (optional)
cp backend/.env.example backend/.env
Leave it as it is to run in demo mode. You can connect a model later in the browser’s Settings dialog, or with .env variables (see Connecting a language model).
3. Run
Use two terminals, both starting from the project root:
# Terminal 1: API on http://localhost:8000
cd backend
uv run uvicorn app.main:app --reload --port 8000
# Terminal 2: website on http://localhost:5173
cd frontend
pnpm dev
Open http://localhost:5173. The badge in the header shows Demo mode, or the live provider, for example External AI · Anthropic · claude-opus-5-5.
4. Build for production (frontend)
cd frontend && pnpm build # outputs static files to frontend/dist
Serve frontend/dist from any static host, and route /api to the FastAPI app, for example with uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 behind a reverse proxy.
On a public server, set TIMEPRESS_SETTINGS=off and configure the model through .env, or set TIMEPRESS_ADMIN_TOKEN and make sure your proxy sends X-Forwarded-For. See Who can open Settings.
Connecting a language model
There are two ways to connect a model. Both support the same providers.
Settings dialog (recommended): click the gear icon at the top right of the site, on the computer that runs the backend.
backend/.env: environment variables, useful for servers and automation.
A default provider that passed its connection test in Settings takes precedence. Otherwise .env is used, and without either, demo mode.
Settings in the browser
Click the gear (Settings) → AI providers.
Choose OpenAI or Anthropic, or click Add provider for a third-party or local service. A custom provider needs a name, an API format (OpenAI-compatible or Anthropic-compatible) and a base URL; plain http:// is allowed only for localhost.
Paste the API key and click Save key. It is stored in your OS keychain (the macOS Keychain on a Mac), and Settings shows only its last four characters. Built-in providers can also use OPENAI_API_KEY / ANTHROPIC_API_KEY from the environment.
Choose a model: type an ID, or click Load models to pick from the provider’s list.
Choose a reasoning level: Provider default sends nothing, otherwise exactly the level you pick.
Click Run connection test. It sends one short fixed message (never a visitor’s event). On success, the provider shows Ready.
The first provider to pass its test becomes the default automatically, and it is marked In use. Use Set as default to switch to another provider. The header badge shows which one is writing, for example External AI · Anthropic · claude-opus-5-5.
Changing the model, reasoning level, key or endpoint requires a new test. There are no silent substitutions: Timepress sends exactly what you chose. The Anthropic refusal fallback is an explicit checkbox, off by default. Providers on your own computer are labelled Local AI, and everything else External AI.
Who can open Settings
By default (TIMEPRESS_SETTINGS=local), only direct requests from the same computer may open Settings: the request must come from a loopback address, carry no proxy forwarding headers, and have a local Host (and a local Origin if one is present). Public visitors never see the gear.
TIMEPRESS_ADMIN_TOKEN=<secret> also allows access from other machines. The dialog asks for the token, which is kept only for the browser session.
TIMEPRESS_SETTINGS=off disables the dialog and its API completely, which is recommended for public deployments.
Configuration through .env
Timepress supports both common model API formats. Each works with the official endpoint or a third-party one:
LLM_API_FORMAT
Official
Third-party / self-hosted
openai (default): /v1/chat/completions
OpenAI: set the key and LLM_MODEL
OpenRouter, DeepSeek, Qwen, Moonshot, Gemini’s OpenAI endpoint, vLLM, Ollama… Also set LLM_BASE_URL.
anthropic: /v1/messages, through the official SDK
Anthropic: the key is enough; the model defaults to claude-opus-5-5
Any gateway that exposes the Messages API. Also set LLM_BASE_URL, plus LLM_AUTH_SCHEME=bearer if it expects Authorization: Bearer.
Reasoning effort: Anthropic output_config.effort (low to max) or OpenAI reasoning_effort (minimal to high); none disables it
LLM_FALLBACKS
on for official Anthropic, otherwise off
Anthropic server-side refusal fallback
LLM_AUTH_SCHEME
x-api-key
Anthropic only: x-api-key or bearer
TIMEPRESS_SETTINGS
local
local (Settings for direct local requests) or off
TIMEPRESS_ADMIN_TOKEN
none
Optional token that unlocks Settings from other machines
Features that third-party gateways often reject are on by default only for the official Anthropic API; enable them for a gateway once you know it supports them. Choose a capable model, because the Chinese editions need fluent Traditional and Simplified Chinese in a period register.
Security: keys live only in the OS keychain or in backend/.env, which git ignores. Neither is ever returned to the browser, and the API shows only the last four characters. Upstream error details (with the key redacted) appear in Settings and in the server log; public visitors see only a short translated message.
Using the site
Sample stories: click a chip under the text box (for example “Century-old lighthouse”), then Publish in this era.
Keyboard:⌘/Ctrl + Enter publishes, and the edition dropdown is a native <select>.
Switching editions reuses the last published event. Editing the text alone never triggers a model call.
Shanghai edition: on wide screens, toggle between 直排 (vertical) and 橫排 (horizontal) typesetting.
Interface language: the globe dropdown at the top right. Your choice is remembered in the browser.
Body {source_text, edition_id}. source_text must be 10–2,000 characters. edition_id is one of london-1890, shanghai-1930, china-1980 or technology-1995.
Errors return {detail, code}. detail is in English. code is stable and translatable: invalid_source_text, invalid_edition, provider_timeout (HTTP 504), provider_unreachable, provider_auth, provider_busy, provider_refused, provider_not_found, provider_bad_request, provider_error or bad_model_output (HTTP 502).
The owner-only Settings API lives under /api/settings. It has endpoints to list providers, add, update or remove them, save or remove a key, list models, run a connection test, and set the default; backend/app/settings_api.py documents each one. GET /api/health also reports the live provider (name, model, local) and whether this browser may open Settings.
Model output is parsed and validated with Pydantic. Malformed output gets one bounded repair attempt. The frontend renders every field as plain text, never as HTML.
Testing
cd backend && uv run pytest # 99 tests
cd frontend && pnpm build && pnpm lint # type-check, build, lint
The backend tests cover:
the sample articles for every story and edition, and input validation;
both API formats, with official and gateway request shapes checked against a mocked HTTP layer;
the JSON repair attempt, refusals and timeouts;
the Settings API: keychain storage, masking, readiness, the default provider and access control;
keys never appearing in responses or logs.
Project structure
timepress/
├── AGENTS.md # Contract for coding agents working on this repo
├── README*.md # This file, in five languages
├── docs/DECISIONS.md # Design and implementation decisions
├── backend/
│ ├── .env.example # Configuration template (copy to .env)
│ ├── app/
│ │ ├── main.py # FastAPI routes, error mapping
│ │ ├── schemas.py # Pydantic request/response contracts
│ │ ├── generation.py # Shared prompt rules, JSON parsing, repair attempt
│ │ ├── editions.py # Edition allowlist and per-edition editorial direction
│ │ ├── providers.py # Configuration + OpenAI-compatible adapter
│ │ ├── anthropic_provider.py # Anthropic Messages API adapter (official SDK)
│ │ ├── provider_factory.py # Picks and caches the adapter for a configuration
│ │ ├── settings_store.py # Provider records + OS keychain storage + readiness
│ │ ├── settings_api.py # Owner-only /api/settings routes and access rules
│ │ └── fixtures.py # Sample stories and demo templates
│ └── tests/
└── frontend/
└── src/
├── App.tsx # Page state, session cache, actions
├── components/ # Composer, edition selector, frame, language switcher, Settings dialog
├── editions/ # One component per edition + catalog.ts
├── i18n/ # Interface translations (en, ja, zh-Hant, zh-Hans, es)
├── lib/ # API clients (articles, settings), types, sample events, date helpers
└── styles/ # base.css (app), editions.css (four page designs)
Content principles
The visitor’s event is the only source of facts. The model must not invent people, quotes, dates, places, numbers or sources. When a detail is missing, it says so rather than filling the gap.
Modern subjects stay modern. A smartphone is still a smartphone in 1890.
Period flavour is created through conventions such as vocabulary, rhythm and layout, not caricature, stereotypes or political editorialising.
Visitor text is treated as untrusted data. Embedded instructions are ignored.
Every page carries a visible creative reinterpretation label, and sample content is always marked.
Troubleshooting
Symptom
Fix
Address already in use
Another server is on that port. Run lsof -nP -iTCP:8000 -sTCP:LISTEN, then kill <PID>.
cd: no such file or directory: frontend
Run each server in its own terminal, starting from the project root.
Header shows Press server offline
The backend isn’t running on port 8000.
Still in demo mode after adding a key
In Settings, the provider must be ReadyandDefault. For .env, restart the backend and check LLM_API_FORMAT, plus LLM_MODEL for openai.
No gear icon
Open the site on the computer running the backend (http://localhost:5173), or set TIMEPRESS_ADMIN_TOKEN. Check that TIMEPRESS_SETTINGS isn’t off.
“No OS keychain” in Settings
The server has no keychain backend (common on headless Linux). Put the key in backend/.env instead.
macOS asks to allow keychain access
Choose Always Allow for the Python process that runs the backend.
HTTP 502 with provider_error from a gateway
It may not support a feature. Set LLM_JSON_MODE, LLM_EFFORT=none or LLM_FALLBACKS=false; the server log shows the upstream reason.
Limitations and roadmap
Current limitations
The frontend needs the backend running, even in demo mode, because the fixtures live on the server.
Sample events are listed in both frontend/src/lib/examples.ts and backend/app/fixtures.py, and must be kept in sync by hand.
Long generated Shanghai articles scroll horizontally inside their vertical frame.
Possible next steps
Side-by-side comparison of two editions.
More researched editions, such as Japanese and other East Asian press traditions.
Shareable snapshots and image export generated from the DOM.
Optional notes explaining why a phrase or layout is typical of its era.
Timepress
English · 日本語 · 繁體中文 · 简体中文 · Español
Timepress turns a short event you write into a news article, then shows how a newspaper or magazine from another era and culture would have reported it. Choose the 1890s London press, a 1930s Shanghai daily, a 1980s Chinese national paper or a 1990s tech magazine. Both the writing and the page design change with the edition.
Every page is an openly fictional creative reinterpretation, never an archival article. Your event is the only source of facts.
Contents
How it works
With no model configured, the site runs in a clearly labelled demo mode, so it can be shown without any API key.
Features
langattributes,prefers-reduced-motionsupport, and a single-column layout on phones.Editions
Every masthead is an original, fictional name. Real papers served only as research inspiration.
Tech stack
httpxfor OpenAI-compatible endpoints; the officialanthropicSDK for the Anthropic Messages APIkeyring(Settings dialog), orbackend/.envpnpm(JavaScript),uv(Python),oxlint,pytestIn development, Vite proxies
/apito the backend, so API keys never reach the browser.Getting started
Prerequisites
1. Install
2. Configure (optional)
Leave it as it is to run in demo mode. You can connect a model later in the browser’s Settings dialog, or with
.envvariables (see Connecting a language model).3. Run
Use two terminals, both starting from the project root:
Open http://localhost:5173. The badge in the header shows Demo mode, or the live provider, for example External AI · Anthropic · claude-opus-5-5.
4. Build for production (frontend)
Serve
frontend/distfrom any static host, and route/apito the FastAPI app, for example withuv run uvicorn app.main:app --host 0.0.0.0 --port 8000behind a reverse proxy.On a public server, set
TIMEPRESS_SETTINGS=offand configure the model through.env, or setTIMEPRESS_ADMIN_TOKENand make sure your proxy sendsX-Forwarded-For. See Who can open Settings.Connecting a language model
There are two ways to connect a model. Both support the same providers.
backend/.env: environment variables, useful for servers and automation.A default provider that passed its connection test in Settings takes precedence. Otherwise
.envis used, and without either, demo mode.Settings in the browser
http://is allowed only forlocalhost.OPENAI_API_KEY/ANTHROPIC_API_KEYfrom the environment.Changing the model, reasoning level, key or endpoint requires a new test. There are no silent substitutions: Timepress sends exactly what you chose. The Anthropic refusal fallback is an explicit checkbox, off by default. Providers on your own computer are labelled Local AI, and everything else External AI.
Who can open Settings
TIMEPRESS_SETTINGS=local), only direct requests from the same computer may open Settings: the request must come from a loopback address, carry no proxy forwarding headers, and have a localHost(and a localOriginif one is present). Public visitors never see the gear.TIMEPRESS_ADMIN_TOKEN=<secret>also allows access from other machines. The dialog asks for the token, which is kept only for the browser session.TIMEPRESS_SETTINGS=offdisables the dialog and its API completely, which is recommended for public deployments.Configuration through
.envTimepress supports both common model API formats. Each works with the official endpoint or a third-party one:
LLM_API_FORMATopenai(default):/v1/chat/completionsLLM_MODELLLM_BASE_URL.anthropic:/v1/messages, through the official SDKclaude-opus-5-5LLM_BASE_URL, plusLLM_AUTH_SCHEME=bearerif it expectsAuthorization: Bearer.Put the settings in
backend/.env. Example setups:Restart the backend after editing
.env, because auto-reload only watches code. Then confirm the mode:All settings
LLM_API_FORMATopenaiopenaioranthropicLLM_BASE_URL/v1is fine for Anthropic.LLM_API_KEYLLM_MODELclaude-opus-5-5for AnthropicopenaiDEMO_MODEfalsetrueserves labelled sample content even when a model is configuredLLM_TIMEOUT_SECONDS60LLM_JSON_MODEresponse_format/output_config.format)LLM_EFFORTmediumfor official Anthropic, otherwise not sentoutput_config.effort(lowtomax) or OpenAIreasoning_effort(minimaltohigh);nonedisables itLLM_FALLBACKSLLM_AUTH_SCHEMEx-api-keyx-api-keyorbearerTIMEPRESS_SETTINGSlocallocal(Settings for direct local requests) oroffTIMEPRESS_ADMIN_TOKENFeatures that third-party gateways often reject are on by default only for the official Anthropic API; enable them for a gateway once you know it supports them. Choose a capable model, because the Chinese editions need fluent Traditional and Simplified Chinese in a period register.
Security: keys live only in the OS keychain or in
backend/.env, which git ignores. Neither is ever returned to the browser, and the API shows only the last four characters. Upstream error details (with the key redacted) appear in Settings and in the server log; public visitors see only a short translated message.Using the site
<select>.Demo mode
Without credentials, the API serves clearly labelled fixtures:
API reference
GET /api/health{status, mode: "generated" | "fixture", editions, provider, settings}POST /api/generate{source_text, edition_id}.source_textmust be 10–2,000 characters.edition_idis one oflondon-1890,shanghai-1930,china-1980ortechnology-1995.A successful response:
Errors return
{detail, code}.detailis in English.codeis stable and translatable:invalid_source_text,invalid_edition,provider_timeout(HTTP 504),provider_unreachable,provider_auth,provider_busy,provider_refused,provider_not_found,provider_bad_request,provider_errororbad_model_output(HTTP 502).The owner-only Settings API lives under
/api/settings. It has endpoints to list providers, add, update or remove them, save or remove a key, list models, run a connection test, and set the default;backend/app/settings_api.pydocuments each one.GET /api/healthalso reports the liveprovider(name, model, local) and whether this browser may open Settings.Model output is parsed and validated with Pydantic. Malformed output gets one bounded repair attempt. The frontend renders every field as plain text, never as HTML.
Testing
The backend tests cover:
Project structure
Content principles
Troubleshooting
Address already in uselsof -nP -iTCP:8000 -sTCP:LISTEN, thenkill <PID>.cd: no such file or directory: frontend.env, restart the backend and checkLLM_API_FORMAT, plusLLM_MODELforopenai.http://localhost:5173), or setTIMEPRESS_ADMIN_TOKEN. Check thatTIMEPRESS_SETTINGSisn’toff.backend/.envinstead.provider_errorfrom a gatewayLLM_JSON_MODE,LLM_EFFORT=noneorLLM_FALLBACKS=false; the server log shows the upstream reason.Limitations and roadmap
Current limitations
frontend/src/lib/examples.tsandbackend/app/fixtures.py, and must be kept in sync by hand.Possible next steps
Contributing
AGENTS.md(the project contract) anddocs/DECISIONS.md.pnpmfor the frontend anduvfor the backend. Don’t switch package managers.frontend/src/i18n/locales/. TheMessagestype makes a missing key a compile error.backend/.envor any API key.License
No license has been chosen yet. Until one is added, all rights are reserved by the authors.