diff --git a/README.md b/README.md
index 22e2dde9..1ef73292 100644
--- a/README.md
+++ b/README.md
@@ -1,32 +1,421 @@
# Oxynote
-**The brains behind your team's technical operations.**
+**Your system, with subtitles.**
-Oxynote is a collaborative platform where runbooks, API docs, and live
-metrics live in one place — think Notion's editing experience crossed with
-Grafana's view of your systems, with more of the ops stack on the roadmap.
+[](LICENSE)
+[](https://github.com/oxynote/oxynote/releases)
+[](https://github.com/oxynote/oxynote/actions)
-- **Runbooks that stay alive** — real-time collaborative documents with
- branches, reviews, and merging, so operational knowledge is maintained
- like code, not lost in a wiki.
-- **API docs, Stripe-style** — a block-based editor (code, diagrams,
- callouts, split documentation) built for polished, structured reference
- pages.
-- **Live data where you read** — embed Prometheus metrics and SQL queries
- (PostgreSQL, MySQL/MariaDB) directly in documents, watch URLs and
- container images, track GitHub activity, and ask the built-in AI
- assistant.
+
+
+
+
-## AI assistant and MCP server
-Oxynote has a built-in AI assistant (works with models from Anthropic, OpenAI, Google, Ollama or
-OpenRouter) and an MCP server that lets Claude Code and other MCP clients
-work with your documents. Setup: [docs/ai.md](docs/ai.md).
+## Why Oxynote?
-## Contributing
+Running software comes with a lot of screens. Grafana or Datadog draws
+the charts of your metrics, Notion or Confluence holds the pages that are
+supposed to explain them, feature flags sit in yet another service, and
+admin actions live in a folder of scripts. None of these products is bad
+at its own job. The trouble starts the moment you need two of them at
+once, which is every time something breaks.
-See [CONTRIBUTING.md](CONTRIBUTING.md) for running Oxynote from source.
+Each of them does a little of the others' job, and you can tell it was an
+afterthought. Grafana lets you describe a panel, but the text goes into a
+tooltip behind an info icon, and Grafana's own docs suggest keeping it
+short. Notion takes a Grafana link, and a link is what you get: an embedded
+panel only renders for someone already signed in to Grafana, and not at all
+on Grafana Cloud. Both keep a history of their own side, tracked separately,
+so you can see that the chart changed and that the page changed, never that
+they changed together, and nobody had to properly review or approve either.
-## License
+So every time you learn something about a chart, you face the same
+small decision: does this go into Grafana, into Notion, or into the Slack
+thread where you are already typing? Usually the thread wins, and the
+knowledge stays there. Oxynote removes the decision. The chart is a block
+in the page, the explanation is the paragraph next to it, and there is
+nowhere else for it to go. If the page matters, a change goes through a
+review like a pull request does: a draft branch, a diff, an approval,
+then a merge.
-[Apache 2.0](LICENSE)
+
+The long version is a bit of a rant, but it explains the personal why
+
+For a long time, the most reliable documentation at my company was the
+Slack chat I had with myself. How it got that way starts with
+dashboards. We had a lot of them: Grafana, Sentry, for some reason a bit
+of Datadog, and a few custom self-hosted HTML pages with charts on them.
+On top of that, I was encouraged to run my own health checks for the
+deployments I owned (why that could not live in Grafana or anything like
+it, nobody could tell me). Those deployments were services pulling
+financial market data over websockets. Under heavy volume they leaked
+memory and got OOM killed. There was a bug in there somewhere that we
+were chasing and could not find for a while, and in the meantime every
+kill meant missed events, and missed events meant running backfill
+scripts by hand. None of our dashboards showed any of that. So: cron, curl
+against each service's status endpoint, and the result posted to me
+privately on Slack. Not convenient at all.
+
+Then there was everything else. Internal wikis still describing
+deployment procedures for trial products that had ended. Secret vaults.
+Server management commands and curl requests. None of it lived in one
+place, and access was its own problem: each of these had a different
+owner who could grant it, so a lot of my time went into finding out who
+that was and then finding them. Since we talked on Slack, that is where
+I asked for all of it, and that is where people answered, sometimes with
+plain credentials (the horror). To not lose any of it, I forwarded those
+messages to the chat with myself, which is how that chat became my
+source of truth for a long time: links to the specific Grafana
+dashboards that mattered out of the many we had, example curl requests
+against other teams' services so I could match their responses when
+integrating (neither the requests nor the responses were documented
+anywhere), and server IPs with notes on what was deployed where.
+
+Naturally, I did try to fix it. I wrote Notion docs and linked the
+Grafana charts from them, and for a while that was fine. Then they
+drifted apart. One doc explained the labels on our ingestion-rate chart,
+market, region and so on, but new labels kept being added, nobody
+documented them, half were not even used in the chart, and each one cost
+Prometheus another time series. Keeping a doc and a chart in sync meant
+links on both sides, in Notion and in Grafana, which was more work than
+I wanted, plus switching browser tabs just to check that the explanation
+and the chart still meant the same thing. And that was only
+observability. Beyond it, admin actions were specific curl requests, and
+feature flags lived in a service disconnected from everything else. All
+of it deserved a proper review flow, like on GitHub, with diffs that work
+for text, metric charts and other visual structures. Instead it had
+none.
+
+David ([@davseby](https://github.com/davseby)), who started Oxynote with me
+([@swithek](https://github.com/swithek)), was the one who had to work
+through a system and its documents the first time he was on call, at a
+different company. He had years of Grafana and internal docs behind him and
+expected it to be routine. What he got was Datadog, which he had never used,
+and a wildly different Notion structure. Then, on that first shift, the
+services that enrich image metadata failed. Search over user uploads turned
+inaccurate, and from there it cascaded through the product. Monitors fired,
+but none of them linked anywhere or said which service had started failing,
+which made for a nerve-racking night of tying things together by hand. The
+metric showing one service overloaded and unresponsive existed the whole
+time. Datadog's UI is convoluted, though, and nothing explained what any
+chart tracked, so David never found it. An AI agent connected to Datadog's
+MCP server did.
+
+What both of us wanted was the graph, the explanation, the command and
+the flag on one page, edited together, reviewed like code, and readable
+by a tired person at night or by an agent. Every tool we had was built
+to be great at one of those things and to never look sideways at the
+others. That gap is what Oxynote is for. It starts with the pieces we
+needed most: live Notion-like docs, metric charts, review flows, and
+Stripe-like split docs for real-time systems. The other pieces of the
+puzzle are coming.
+
+
+
+## What Oxynote is
+
+Think of Oxynote as a Notion-style workspace that knows what a running
+system is. A page is still a page: you type, others see it as you type,
+blocks move around. The difference is what a block can be. One block is a
+chart that runs a Prometheus query, or a SQL query against PostgreSQL,
+MySQL or MariaDB, every time the page opens. The paragraph above it says
+what the chart means. Because the chart is part of the page rather than a
+link to somewhere else, there is no link to maintain and no second place
+to check.
+
+The same idea carries into API docs. Stripe made the two-column reference
+the standard, parameters on the left, request and response on the right,
+and public doc generators copied it years ago. Internal tools never did,
+which is why the docs for your own services end up as a wiki table. In
+Oxynote that layout is a block called *Split Documentation*, and the right
+column can hold code or a live chart, so the docs for a service and the
+health of that service share a page.
+
+When a page matters, you can put it under review. Changes go into a
+draft, the diff shows text and charts changing side by side, reviewers
+approve, and the draft merges into main. Pages that do not need that stay
+plain.
+
+And the reader is not always a person. Rubber Duck, the built-in
+assistant, edits pages and runs the same queries the blocks run. You pick
+its model: Anthropic, OpenAI, Google, OpenRouter, or a local one through
+Ollama. MCP clients such as Claude Code, Codex and many others get the
+same tools over OAuth, with whatever model they already run. People and
+agents edit the same pages, and the pages stay designed for people.
+
+## Quick start
+
+Oxynote runs as one container next to a PostgreSQL database you already
+have. It needs to know two things: the address people will open in the
+browser, and how to reach that database.
+
+```sh
+docker run -d --name oxynote \
+ -p 8080:8080 \
+ -e OXYNOTE_PUBLIC_URL=http://localhost:8080 \
+ -e OXYNOTE_DB_DSN='postgresql://oxynote:change-me@postgres.example.com/oxynote?sslmode=disable' \
+ -v oxynote_data:/oxynote/data \
+ ghcr.io/oxynote/oxynote:latest
+```
+
+Open the address and sign up. Until you configure an email sender, the
+verification link shows up in the container's logs instead of your inbox.
+Keep the data volume: it holds your uploads and the secrets that keep
+everyone signed in and every data source connected. For a real domain, put a
+TLS-terminating proxy in front and set the public URL to the `https://`
+address.
+
+
+Docker Compose with PostgreSQL, Meilisearch for search and Mailpit for mail
+
+```yaml
+name: oxynote
+
+services:
+ oxynote:
+ image: ghcr.io/oxynote/oxynote:latest
+ ports:
+ - "8080:8080"
+ environment:
+ - OXYNOTE_PUBLIC_URL=http://localhost:8080
+ - OXYNOTE_DB_DSN=postgresql://oxynote:change-me-db@postgres/oxynote?sslmode=disable
+ - OXYNOTE_MEILISEARCH_URL=http://meilisearch:7700
+ - OXYNOTE_MEILISEARCH_MASTER_KEY=change-me-meili-key
+ # point these at a real relay to have mail delivered; here they go
+ # to the mailpit service below.
+ - OXYNOTE_SMTP_DSN=smtp://mailpit:1025?tls=none
+ - OXYNOTE_EMAIL_FROM_ADDRESS=Oxynote
+ volumes:
+ - oxynote_data:/oxynote/data
+ depends_on:
+ postgres:
+ condition: service_healthy
+ meilisearch:
+ condition: service_healthy
+ mailpit:
+ condition: service_started
+ # the image shuts its services down in order, flushing open documents
+ # last; docker's default 10s grace would kill that mid-flush.
+ stop_grace_period: 60s
+ restart: unless-stopped
+
+ postgres:
+ image: postgres:18.6-alpine
+ environment:
+ - POSTGRES_USER=oxynote
+ - POSTGRES_PASSWORD=change-me-db
+ - POSTGRES_DB=oxynote
+ volumes:
+ - postgres_data:/var/lib/postgresql
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U oxynote -d oxynote"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ restart: unless-stopped
+
+ # full-text search.
+ meilisearch:
+ image: getmeili/meilisearch:v1.53.1
+ environment:
+ - MEILI_ENV=production
+ - MEILI_MASTER_KEY=change-me-meili-key
+ - MEILI_NO_ANALYTICS=true
+ volumes:
+ - meilisearch_data:/meili_data
+ healthcheck:
+ test: ["CMD", "curl", "--fail", "http://localhost:7700/health"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ restart: unless-stopped
+
+ # catches every mail Oxynote sends and shows it at
+ # http://localhost:8025.
+ mailpit:
+ image: axllent/mailpit:v1.31.0
+ ports:
+ - "8025:8025"
+ restart: unless-stopped
+
+volumes:
+ oxynote_data:
+ postgres_data:
+ meilisearch_data:
+```
+
+
+
+Everything else, from search and email to object storage, the GitHub
+and Slack apps, social login and Rubber Duck, is one variable away and
+listed in [docker/prod/README.md](docker/prod/README.md). To run from
+source, see [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## What is in the box
+
+### Metric grids
+
+
+
+
+
+
+A `Live Metrics` block is one query and one chart: a time series, a bar
+chart or a gauge, fed by Prometheus or by SQL against PostgreSQL, MySQL or
+MariaDB. Put several of those
+blocks in a grid and you have something as dense as a Grafana dashboard, if
+that is what you want. What Grafana does not give you is the text around the
+grid: what each chart means, why the threshold sits where it does, and what
+to do when it is crossed, right next to the charts, with no question of
+where it should go.
+
+### Reviews
+
+
+
+
+
+
+Any page can be made reviewable, and from then on every change goes
+into a draft. The diff understands blocks, not just text: a renamed
+endpoint, a renamed response field and a reworded sentence show up in one
+view, red and green, in place. Reviewers approve, the draft merges into
+main, and main can be protected so nobody edits it directly.
+
+### Collaboration
+
+
+
+
+
+
+Pages are edited live, so you see who is on the page, which block they
+are holding and where they are typing, and nobody overwrites anybody.
+Comments attach to a word, to a line in a diagram's source or to a whole
+chart, carry replies and get resolved when the change lands. Every
+mention and reply arrives in your inbox.
+
+### The rest
+
+- Blocks: headings, lists, checklists, quotes, titled code blocks,
+ callouts, images, files, Figma embeds, Mermaid diagrams.
+- Freshness hooks: a block or a whole page declares what it depends on,
+ and Oxynote tells you when that changes. Point a paragraph about a
+ deploy procedure at the files on GitHub it describes, at the container
+ image it names, at a web page, or at a date. When the target changes,
+ the block is highlighted, the page loses freshness, and the maintainers
+ get a reminder to re-read the text and approve it again.
+- A page tree with sub pages, tags, full-text search, keyboard shortcuts
+ for everything, light and dark themes.
+- Workspaces with invitations, GitHub and Slack apps, and social login
+ through GitHub, Google or Slack.
+
+## Simple on purpose
+
+A few honest words about who this is for.
+
+Oxynote is for you if:
+
+- you explain systems to other tech people and want the metric chart and
+ the explanation on one page
+- you want Stripe-style docs for internal services without a doc
+ generator
+- you want changes to a page to go through a draft, a diff and an
+ approval, like a pull request
+- you want one page that your team and your agents both read
+- you self-host
+
+Probably not for you if:
+
+- you want a hosted service. There is none for now
+- you want a wiki for the whole company. This is for tech people and the
+ systems they run
+- you want every option under the sun. Oxynote is opinionated, ships good
+ defaults and keeps the option count low
+
+## Where this is going
+
+Right now Oxynote covers the writing and reading side: pages, code, live
+charts, *Split Documentation*, drafts with diffs and approvals, comments,
+and Rubber Duck with an MCP server.
+
+Next come the pieces that still live in other tools. Logs in the page,
+so the chart, the log line and the explanation sit together. Feature
+flags as blocks with an OpenFeature SDK, so a flag is documented,
+reviewed and flipped in one place instead of a service nobody links to.
+Admin actions as blocks, so the curl request nobody wants to paste into
+Slack becomes a button with a review and an audit trail. Suggestions in
+review, so a reviewer proposes the fix instead of describing it. Code
+references, a side panel tying a block to the code behind it: the
+function that emits the metric, the handler behind the endpoint, every
+call site of a flag, indexed with tree-sitter so it needs no model at
+runtime. Blocks for AI teams: prompts, evals and token usage next to the
+RFC that changed them.
+
+One surface for everything around a running system, simple for the
+person who has to decide and for the agent helping them.
+
+### Roadmap
+
+**Pages**
+
+- ✅ Real-time collaborative editor with headings, lists, code, callouts,
+ images, files, Figma embeds and Mermaid diagrams
+- ✅ Split Documentation block for Stripe-style API docs
+- ✅ Page tree, tags, full-text search, keyboard shortcuts
+
+**Live data in pages**
+
+- ✅ Metric charts from Prometheus queries
+- ✅ Metric charts from SQL queries against PostgreSQL, MySQL and MariaDB
+- ✅ Metric grids, dashboard-style, with the explanation around them
+- ⚪ Log blocks backed by Loki, next to the charts
+
+**Review**
+
+- ✅ Drafts with a diff of text and charts, approvals and merge into main
+- ✅ Protected pages that only change through a merged draft
+- ✅ Comments on text, diagrams and charts, with an inbox
+- ✅ Page history
+- ⚪ Edit suggestions: a reviewer proposes the change, the author accepts
+ it
+
+**Operations**
+
+- ✅ Freshness hooks: a block or page tracks the GitHub files, container
+ image, web page or date it depends on and is flagged when that changes
+- ⚪ Feature flag blocks with an OpenFeature SDK
+- ⚪ Admin action blocks: a reviewed button instead of a curl request in
+ Slack
+- ⚪ Code references: a panel tying a block to the function, handler or
+ call site behind it
+
+**Humans and agents**
+
+- ✅ Rubber Duck, the built-in assistant, with your choice of model
+- ✅ MCP server, so Claude Code and other clients work on your pages
+- ⚪ Blocks for AI teams: prompts, evals, token usage, agent topology
+
+**Running it**
+
+- ✅ One container plus PostgreSQL
+- ✅ GitHub and Slack apps, social login
+
+## FAQ
+
+**Why not Notion plus Grafana?** I did exactly that. A Notion doc with a
+link to the Grafana chart, and then (because you also want to get back)
+a link from the chart to the doc. Then you keep both tabs open to check
+they still say the same thing, and at some point you stop doing that,
+and now you own a doc that lies a little. In Oxynote the metric chart is
+a block inside the doc. Nothing to link, nothing to check.
+
+**Why the name?** Internally we first called this "breathing docs".
+Breathing needs oxygen, so oxy became the prefix, and note followed.
+
+---
+
+Oxynote is built by Simon ([@swithek](https://github.com/swithek)) and
+David ([@davseby](https://github.com/davseby)), the authors of
+[ttlcache](https://github.com/jellydator/ttlcache).
diff --git a/docs/images/collaboration-dark.png b/docs/images/collaboration-dark.png
new file mode 100644
index 00000000..7860bca0
Binary files /dev/null and b/docs/images/collaboration-dark.png differ
diff --git a/docs/images/collaboration-light.png b/docs/images/collaboration-light.png
new file mode 100644
index 00000000..0ff771f8
Binary files /dev/null and b/docs/images/collaboration-light.png differ
diff --git a/docs/images/hero-dark.png b/docs/images/hero-dark.png
new file mode 100644
index 00000000..d46ae8d8
Binary files /dev/null and b/docs/images/hero-dark.png differ
diff --git a/docs/images/hero-light.png b/docs/images/hero-light.png
new file mode 100644
index 00000000..925ca761
Binary files /dev/null and b/docs/images/hero-light.png differ
diff --git a/docs/images/metric-grid-dark.png b/docs/images/metric-grid-dark.png
new file mode 100644
index 00000000..a9a1d4d7
Binary files /dev/null and b/docs/images/metric-grid-dark.png differ
diff --git a/docs/images/metric-grid-light.png b/docs/images/metric-grid-light.png
new file mode 100644
index 00000000..37203fef
Binary files /dev/null and b/docs/images/metric-grid-light.png differ
diff --git a/docs/images/review-dark.png b/docs/images/review-dark.png
new file mode 100644
index 00000000..1147f090
Binary files /dev/null and b/docs/images/review-dark.png differ
diff --git a/docs/images/review-light.png b/docs/images/review-light.png
new file mode 100644
index 00000000..2572a540
Binary files /dev/null and b/docs/images/review-light.png differ