initial commit
This commit is contained in:
commit
23816d00f7
71 changed files with 14497 additions and 0 deletions
229
data/docs/go-doc.md
Normal file
229
data/docs/go-doc.md
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue