229 lines
8.3 KiB
Markdown
229 lines
8.3 KiB
Markdown
|
|
# go-doc
|
||
|
|
|
||
|
|
**go-doc** is a flat-file documentation and audience-feedback platform. It isolates guidance for
|
||
|
|
three audiences inside a single Markdown document, lets anyone annotate that document as a **User 🟢**,
|
||
|
|
**Operator 🔵** or **Developer 🔴**, and stores everything as plain files you can read, diff and commit.
|
||
|
|
|
||
|
|
```
|
||
|
|
data/docs/go-doc.md # raw Markdown, one file per document
|
||
|
|
data/comments/go-doc-comments.json # JSON array of audience feedback
|
||
|
|
```
|
||
|
|
|
||
|
|
One file carries all three viewpoints. Sections are delimited by level-2 headings and classified by the
|
||
|
|
audience they address, so the reader renders the tab that matters to you while the shared preamble
|
||
|
|
stays visible to everyone. Feedback badges are derived **server-side** from the stored role, so a
|
||
|
|
client cannot spoof another audience's colour by submitting a `badge` field.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## For Users
|
||
|
|
|
||
|
|
You use the product. You are not deploying it and you are not extending it — you want the shortest
|
||
|
|
path from "I have an account" to "the thing I came here to do actually worked".
|
||
|
|
|
||
|
|
### Getting oriented
|
||
|
|
|
||
|
|
Open the app and you land on the document index: every Markdown file in `data/docs/`, newest first.
|
||
|
|
Each entry shows its size, last modification date and how much audience feedback it has collected.
|
||
|
|
|
||
|
|
### Switching to your view
|
||
|
|
|
||
|
|
Every document has three tabs along the top:
|
||
|
|
|
||
|
|
| Tab | Badge | What it shows you |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| User | 🟢 | Day-to-day workflows and everyday tasks |
|
||
|
|
| Operator | 🔵 | Deployment, health checks, rollback |
|
||
|
|
| Developer | 🔴 | API contracts, schemas, integration notes |
|
||
|
|
|
||
|
|
Switching tabs swaps the guidance without duplicating content across files. The preamble above —
|
||
|
|
this section — always stays visible.
|
||
|
|
|
||
|
|
### Leaving feedback
|
||
|
|
|
||
|
|
The right-hand panel is your feedback channel. Pick the badge that matches who you are, write your
|
||
|
|
note, and post it. It is stored as one entry in a JSON array next to the document, so your note is
|
||
|
|
never lost and never overwritten by someone else's.
|
||
|
|
|
||
|
|
Filter the list by any badge to focus on the feedback relevant to you. Comments carry the author,
|
||
|
|
the role, the badge and a timestamp, and you can delete your own.
|
||
|
|
|
||
|
|
### Editing a document
|
||
|
|
|
||
|
|
Press **Edit** to open the hybrid editor. It has two synchronised views:
|
||
|
|
|
||
|
|
- **Visual Edit (WYSIWYG)** — a rich surface where bold, italic, inline code, headings, lists,
|
||
|
|
quotes and links are applied directly. The Markdown underneath updates as you type.
|
||
|
|
- **Source Code (Markdown)** — the raw Markdown with a live preview beside it.
|
||
|
|
|
||
|
|
Both views write to the same string, so you can start in one and finish in the other. `Ctrl/Cmd+S`
|
||
|
|
saves.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## For Operators
|
||
|
|
|
||
|
|
You keep the thing running. Your concern is state convergence, blast radius, and how fast you can
|
||
|
|
get back to a known-good position when a release goes wrong.
|
||
|
|
|
||
|
|
### Storage layout
|
||
|
|
|
||
|
|
State is a directory, not a database:
|
||
|
|
|
||
|
|
```
|
||
|
|
data/docs/<slug>.md # one Markdown file per document
|
||
|
|
data/comments/<slug>-comments.json # one JSON array per document
|
||
|
|
```
|
||
|
|
|
||
|
|
There is no daemon to restart and no migration to run. Backups are file copies; the directory layout
|
||
|
|
*is* the schema.
|
||
|
|
|
||
|
|
### Write guarantees
|
||
|
|
|
||
|
|
Every mutation is a temp-file write, `fsync`, `rename`, then `fsync` of the parent directory. The
|
||
|
|
consequences matter operationally:
|
||
|
|
|
||
|
|
- A reader never observes a half-written document.
|
||
|
|
- A crash mid-write leaves either the previous file or the new one — never a truncated one.
|
||
|
|
- Feedback read-modify-write cycles are serialised per slug, so a burst of comments cannot interleave
|
||
|
|
and lose entries.
|
||
|
|
|
||
|
|
Temporary files are named `.godoc-tmp-*` and are excluded from the document index. If you ever see
|
||
|
|
one survive a crash, that directory is safe to inspect and safe to delete.
|
||
|
|
|
||
|
|
### Health checks
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -fsS http://localhost:8080/api/health
|
||
|
|
```
|
||
|
|
|
||
|
|
Returns `{"status":"ok","data_root":"...","time":"..."}`. If the process answers at all, the data
|
||
|
|
directory was opened successfully at boot.
|
||
|
|
|
||
|
|
### Store-wide census
|
||
|
|
|
||
|
|
`GET /api/stats` reports total documents, total comments and a per-role breakdown — the fastest way
|
||
|
|
to confirm a restore or a sync actually landed:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{ "docs": 120, "comments": 3000, "by_role": { "user": 1000, "operator": 1000, "developer": 1000 } }
|
||
|
|
```
|
||
|
|
|
||
|
|
### Rollback
|
||
|
|
|
||
|
|
There is nothing to roll back except the files. Restore the previous `data/` snapshot, restart the
|
||
|
|
process, and confirm with the index endpoint. Deletes are permanent and are not journalled, so keep
|
||
|
|
your backups.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## For Developers
|
||
|
|
|
||
|
|
You integrate with it or you change it. You care about contracts, determinism, and failure modes.
|
||
|
|
|
||
|
|
### REST API
|
||
|
|
|
||
|
|
| Method | Path | Purpose |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| `GET` | `/api/health` | Liveness and the resolved data root |
|
||
|
|
| `GET` | `/api/roles` | Role → badge → colour catalogue for the UI |
|
||
|
|
| `GET` | `/api/docs` | Index; `?q=` searches, `?counts=1` adds comment counts |
|
||
|
|
| `GET` | `/api/docs/:slug` | Raw Markdown (`text/markdown`), or `?format=json` |
|
||
|
|
| `POST` | `/api/docs/:slug` | Create or fully replace; `text/markdown` or JSON |
|
||
|
|
| `PUT` | `/api/docs/:slug` | Alias of `POST` |
|
||
|
|
| `DELETE` | `/api/docs/:slug` | Permanent delete; cascades feedback |
|
||
|
|
| `GET` | `/api/docs/:slug/comments` | JSON list of comments |
|
||
|
|
| `POST` | `/api/docs/:slug/comments` | Append `{author, role, content}` |
|
||
|
|
| `DELETE` | `/api/docs/:slug/comments/:id` | Remove one comment |
|
||
|
|
| `GET` | `/api/docs/:slug/stats` | Totals, per-role tally, time bounds |
|
||
|
|
| `GET` | `/api/stats` | Store-wide totals |
|
||
|
|
|
||
|
|
### Slug contract
|
||
|
|
|
||
|
|
Titles are slugified into lower-case, hyphen-separated identifiers:
|
||
|
|
|
||
|
|
```
|
||
|
|
"Core Service API" -> core-service-api
|
||
|
|
"Überwachen der Dienste" -> uberwachen-der-dienste
|
||
|
|
"API_v2 (Internal)" -> api-v2-internal
|
||
|
|
"!!!": -> rejected
|
||
|
|
```
|
||
|
|
|
||
|
|
Diacritics fold to ASCII, separator runs collapse to a single hyphen, the result is trimmed and
|
||
|
|
capped at 120 characters. Slugs containing `/`, `\`, `..`, control characters or NUL bytes are
|
||
|
|
rejected with `400 invalid_slug` before any filesystem call, so a URL can never escape `data/docs`.
|
||
|
|
|
||
|
|
Because normalisation is idempotent, `/api/docs/Core Service API` and `/api/docs/core-service-api`
|
||
|
|
address the same document. The `POST` response echoes the canonical slug — always use that value.
|
||
|
|
|
||
|
|
### Comment contract
|
||
|
|
|
||
|
|
`POST /api/docs/:slug/comments` accepts:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{ "author": "ana", "role": "Developer", "content": "The retry budget is undocumented." }
|
||
|
|
```
|
||
|
|
|
||
|
|
The stored record is authoritative — the badge is derived from the role, and an incoming `badge`
|
||
|
|
field is ignored:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "c1a105f4684c_9b1c081def3b00a4",
|
||
|
|
"author": "ana",
|
||
|
|
"role": "developer",
|
||
|
|
"badge": "🔴",
|
||
|
|
"content": "The retry budget is undocumented.",
|
||
|
|
"timestamp": "2026-10-04T08:07:45.996Z"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Roles are canonicalised case-insensitively (`Developer`, `DEV`, `developer` are the same role) and
|
||
|
|
anything else returns `400 invalid_role`. Empty content returns `400 invalid_comment`. An unset
|
||
|
|
author defaults to the role label. IDs are a millisecond prefix plus 8 random bytes, so they sort
|
||
|
|
roughly chronologically and do not collide under load.
|
||
|
|
|
||
|
|
### Error shapes
|
||
|
|
|
||
|
|
Every failure is JSON, so clients never have to parse an HTML error page:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": { "code": "invalid_role", "message": "storage: invalid role: \"root\" ...", "request_id": "" },
|
||
|
|
"status": 400
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Codes: `not_found`, `invalid_slug`, `invalid_role`, `invalid_comment`, `invalid_body`,
|
||
|
|
`body_too_large`, `internal_error`.
|
||
|
|
|
||
|
|
### Limits
|
||
|
|
|
||
|
|
| Limit | Value |
|
||
|
|
| --- | --- |
|
||
|
|
| Document body | 5 MiB |
|
||
|
|
| Comment body | 256 KiB |
|
||
|
|
| Slug length | 120 characters |
|
||
|
|
| Author length | 120 characters |
|
||
|
|
|
||
|
|
Exceeding the document or comment ceiling returns `400 body_too_large` and writes nothing.
|
||
|
|
|
||
|
|
### Embedding the SPA
|
||
|
|
|
||
|
|
`make build` copies `frontend/dist` into `backend/webui/dist`, where `//go:embed all:dist` picks it
|
||
|
|
up. The compiled binary then serves the SPA on `/` and the API on `/api/*` from one process with no
|
||
|
|
runtime dependencies. Client-side routes such as `/docs/core-service-api` fall back to `index.html`;
|
||
|
|
`/api/*` never does.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Related Documentation
|
||
|
|
|
||
|
|
Other go-* projects documented in this workspace:
|
||
|
|
|
||
|
|
- [go-bananas](go-bananas.md) — Runtime design-token service
|
||
|
|
- [go-guess](go-guess.md) — Infrastructure guessing game
|
||
|
|
- [go-loose](go-loose.md) — Lightweight auth middleware
|
||
|
|
- [go-mad](go-mad.md) — Markdown-driven development
|
||
|
|
- [go-places](go-places.md) — Location-aware services
|
||
|
|
- [go-tell](go-tell.md) — Content CMS
|