* deps(web): bump the safe group across 1 directory with 29 updates Bumps the safe group with 29 updates in the /web directory: | Package | From | To | | --- | --- | --- | | [@internationalized/date](https://github.com/adobe/react-spectrum) | `3.12.3` | `3.12.4` | | [@pinia/colada](https://github.com/posva/pinia-colada) | `1.4.2` | `1.4.4` | | [@pinia/colada-nuxt](https://github.com/posva/pinia-colada) | `1.0.2` | `1.1.0` | | [@sentry/nuxt](https://github.com/getsentry/sentry-javascript) | `10.72.0` | `10.73.0` | | [@tiptap/extension-code-block-lowlight](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-code-block-lowlight) | `3.30.5` | `3.31.3` | | [@tiptap/extension-collaboration](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-collaboration) | `3.30.5` | `3.31.3` | | [@tiptap/extension-collaboration-caret](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-collaboration-caret) | `3.30.5` | `3.31.3` | | [@tiptap/extension-file-handler](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-file-handler) | `3.30.5` | `3.31.3` | | [@tiptap/extension-list](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-list) | `3.30.5` | `3.31.3` | | [@tiptap/extension-node-range](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-node-range) | `3.30.5` | `3.31.3` | | [@tiptap/extension-text-style](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-text-style) | `3.30.5` | `3.31.3` | | [@tiptap/extension-typography](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-typography) | `3.30.5` | `3.31.3` | | [@tiptap/extensions](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension) | `3.30.5` | `3.31.3` | | [@tiptap/pm](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/pm) | `3.30.5` | `3.31.3` | | [@tiptap/starter-kit](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/starter-kit) | `3.30.5` | `3.31.3` | | [@tiptap/suggestion](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/suggestion) | `3.30.5` | `3.31.3` | | [@tiptap/vue-3](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/vue-3) | `3.30.5` | `3.31.3` | | [vue-codemirror6](https://github.com/logue/vue-codemirror6) | `1.6.1` | `1.6.2` | | [vue-echarts](https://github.com/ecomfe/vue-echarts) | `8.1.0` | `8.3.0` | | [@iconify-json/hugeicons](https://github.com/iconify/icon-sets) | `1.2.33` | `1.2.34` | | [@iconify-json/lucide](https://github.com/iconify/icon-sets) | `1.2.126` | `1.2.129` | | [@iconify-json/simple-icons](https://github.com/iconify/icon-sets) | `1.2.93` | `1.2.94` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.68.0` | `8.69.0` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.68.0` | `8.69.0` | | [electron](https://github.com/electron/electron) | `44.0.0` | `44.2.0` | | [eslint](https://github.com/eslint/eslint) | `10.9.1` | `10.10.0` | | [happy-dom](https://github.com/capricorn86/happy-dom) | `20.11.13` | `20.14.0` | | [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `6.33.0` | `6.34.0` | | [playwright](https://github.com/microsoft/playwright) | `1.62.1` | `1.63.0` | Updates `@internationalized/date` from 3.12.3 to 3.12.4 - [Release notes](https://github.com/adobe/react-spectrum/releases) - [Commits](https://github.com/adobe/react-spectrum/compare/@internationalized/date@3.12.3...@internationalized/date@3.12.4) Updates `@pinia/colada` from 1.4.2 to 1.4.4 - [Release notes](https://github.com/posva/pinia-colada/releases) - [Changelog](https://github.com/posva/pinia-colada/blob/main/CHANGELOG.md) - [Commits](https://github.com/posva/pinia-colada/compare/v1.4.2...v1.4.4) Updates `@pinia/colada-nuxt` from 1.0.2 to 1.1.0 - [Release notes](https://github.com/posva/pinia-colada/releases) - [Changelog](https://github.com/posva/pinia-colada/blob/main/CHANGELOG.md) - [Commits](https://github.com/posva/pinia-colada/compare/@pinia/colada-nuxt@1.0.2...@pinia/colada-nuxt@1.1.0) Updates `@sentry/nuxt` from 10.72.0 to 10.73.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/10.73.0/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.72.0...10.73.0) Updates `@tiptap/extension-code-block-lowlight` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/extension-code-block-lowlight/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension-code-block-lowlight) Updates `@tiptap/extension-collaboration` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/extension-collaboration/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension-collaboration) Updates `@tiptap/extension-collaboration-caret` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/extension-collaboration-caret/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension-collaboration-caret) Updates `@tiptap/extension-file-handler` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/extension-file-handler/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension-file-handler) Updates `@tiptap/extension-list` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/extension-list/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension-list) Updates `@tiptap/extension-node-range` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/extension-node-range/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension-node-range) Updates `@tiptap/extension-text-style` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/extension-text-style/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension-text-style) Updates `@tiptap/extension-typography` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/extension-typography/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension-typography) Updates `@tiptap/extensions` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/extension) Updates `@tiptap/pm` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/pm/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/pm) Updates `@tiptap/starter-kit` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/starter-kit/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/starter-kit) Updates `@tiptap/suggestion` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/suggestion/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/suggestion) Updates `@tiptap/vue-3` from 3.30.5 to 3.31.3 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/v3.31.3/packages/vue-3/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.31.3/packages/vue-3) Updates `vue-codemirror6` from 1.6.1 to 1.6.2 - [Release notes](https://github.com/logue/vue-codemirror6/releases) - [Changelog](https://github.com/logue/vue-codemirror6/blob/master/CHANGELOG.md) - [Commits](https://github.com/logue/vue-codemirror6/commits) Updates `vue-echarts` from 8.1.0 to 8.3.0 - [Release notes](https://github.com/ecomfe/vue-echarts/releases) - [Changelog](https://github.com/ecomfe/vue-echarts/blob/main/CHANGELOG.md) - [Commits](https://github.com/ecomfe/vue-echarts/compare/v8.1.0...v8.3.0) Updates `@iconify-json/hugeicons` from 1.2.33 to 1.2.34 - [Commits](https://github.com/iconify/icon-sets/commits) Updates `@iconify-json/lucide` from 1.2.126 to 1.2.129 - [Commits](https://github.com/iconify/icon-sets/commits) Updates `@iconify-json/simple-icons` from 1.2.93 to 1.2.94 - [Commits](https://github.com/iconify/icon-sets/commits) Updates `@typescript-eslint/eslint-plugin` from 8.68.0 to 8.69.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.69.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.68.0 to 8.69.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.69.0/packages/parser) Updates `electron` from 44.0.0 to 44.2.0 - [Release notes](https://github.com/electron/electron/releases) - [Commits](https://github.com/electron/electron/compare/v44.0.0...v44.2.0) Updates `eslint` from 10.9.1 to 10.10.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.9.1...v10.10.0) Updates `happy-dom` from 20.11.13 to 20.14.0 - [Release notes](https://github.com/capricorn86/happy-dom/releases) - [Commits](https://github.com/capricorn86/happy-dom/compare/v20.11.13...v20.14.0) Updates `knip` from 6.33.0 to 6.34.0 - [Release notes](https://github.com/webpro-nl/knip/releases) - [Commits](https://github.com/webpro-nl/knip/commits/knip@6.34.0/packages/knip) Updates `playwright` from 1.62.1 to 1.63.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.62.1...v1.63.0) --- updated-dependencies: - dependency-name: "@iconify-json/hugeicons" dependency-version: 1.2.34 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: safe - dependency-name: "@iconify-json/lucide" dependency-version: 1.2.129 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: safe - dependency-name: "@iconify-json/simple-icons" dependency-version: 1.2.94 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: safe - dependency-name: "@internationalized/date" dependency-version: 3.12.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: safe - dependency-name: "@pinia/colada" dependency-version: 1.4.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: safe - dependency-name: "@pinia/colada-nuxt" dependency-version: 1.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@sentry/nuxt" dependency-version: 10.73.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extension-code-block-lowlight" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extension-collaboration" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extension-collaboration-caret" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extension-file-handler" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extension-list" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extension-node-range" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extension-text-style" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extension-typography" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/extensions" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/pm" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/starter-kit" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/suggestion" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@tiptap/vue-3" dependency-version: 3.31.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.69.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: safe - dependency-name: "@typescript-eslint/parser" dependency-version: 8.69.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: safe - dependency-name: electron dependency-version: 44.2.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: safe - dependency-name: eslint dependency-version: 10.10.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: safe - dependency-name: happy-dom dependency-version: 20.14.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: safe - dependency-name: knip dependency-version: 6.34.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: safe - dependency-name: playwright dependency-version: 1.63.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: safe - dependency-name: vue-codemirror6 dependency-version: 1.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: safe - dependency-name: vue-echarts dependency-version: 8.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: safe ... Signed-off-by: dependabot[bot] <support@github.com> * fix web lints --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: swithek <52840391+swithek@users.noreply.github.com> |
||
|---|---|---|
| .github | ||
| .vscode | ||
| datagen | ||
| docker | ||
| docs | ||
| e2e | ||
| scripts | ||
| server | ||
| web | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| SECURITY.md | ||
Oxynote
Your system, with subtitles.
Why Oxynote?
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.
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.
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.
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), who started Oxynote with me (@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 on 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 is one container plus a PostgreSQL database. With a database already running, this is all it takes:
docker run -d --name oxynote \
-p 8080:8080 \
-e OXYNOTE_DB_DSN='postgresql://dbusername:dbpass@postgres.example.com/oxynote?sslmode=require' \
-v oxynote_data:/oxynote/data \
ghcr.io/oxynote/oxynote:latest
Open http://localhost:8080 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. To open it at any other
address, set OXYNOTE_PUBLIC_URL to that address, and put a TLS-terminating
proxy in front for a domain.
No PostgreSQL yet? This compose file starts one next to Oxynote, plus Meilisearch for search and Mailpit for email
name: oxynote
services:
oxynote:
image: ghcr.io/oxynote/oxynote:latest
ports:
- "8080:8080"
environment:
- 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 <team@example.com>
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. To run from source, see CONTRIBUTING.md.
What is in the box
Metric grids
A Live Metrics block shows a time series, a bar chart or a gauge, built
from one or more Prometheus or SQL queries, with PostgreSQL, MySQL and
MariaDB supported on the SQL side. A metric grid is several of those blocks
side by side. In Grafana that would be a dashboard. In Oxynote it is a
dashboard inside a page, which means the explanation goes around it: what a
chart shows, why the threshold sits where it does, what to do when it is
crossed, all on the same page as the charts.
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 on 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) and David (@davseby), the authors of ttlcache.