# 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/.md # one Markdown file per document data/comments/-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