# Guides: writing procedures in ShopDB, and generating them from elsewhere A guide is a step-by-step procedure stored in ShopDB: images, working checklists, version history, and printable output. This is the reference for the data model and the API, aimed at somebody generating guides programmatically - from a script, an importer, or a model. Not to be confused with the **knowledgebase** plugin, which is a link registry: a description, a URL and keywords pointing at a document held somewhere else. That one references documents; this one holds them. They stay separate, and share only a tag vocabulary and the global search box. --- ## 1. The shape of a guide A guide is an ordered list of typed **blocks**, stored as JSON. Not HTML, and that is load-bearing: - a tickable step needs a **stable id** to be remembered between visits - **dark mode** needs semantic markup, not pasted colours - **print** needs to know where a step begins so it does not split one across a page Free-form HTML gives none of those, and pasted styling fights the theme. ### Block types | type | Fields | Notes | |---|---|---| | `heading` | `text` | Section heading | | `paragraph` | `text` | Body text | | `step` | `title`, `text` | Numbers itself by position among steps | | `checklist` | `items[]` | The tickable one. Items need their own ids | | `bullets` | `items[]` | Unordered list | | `numbers` | `items[]` | Ordered list | | `callout` | `text`, `title`, `variant` | `variant`: `info`, `warning`, `success` | | `code` | `text`, `language`, `caption` | Never autocorrected; wraps rather than clips | | `image` | `url`, `alt`, `caption` | `url` comes from the image upload endpoint | | `table` | `header[]`, `rows[][]` | Simple grid | Every block needs: ```json { "id": "b1", "type": "paragraph", "text": "..." } ``` **Ids must be unique and stable.** They are what a reader's tick is stored against, so a save without them is refused, and changing one loses that item's progress. Checklist items carry their own ids for the same reason: ```json { "id": "b4", "type": "checklist", "items": [ { "id": "i1", "text": "Confirm the Verified date" } ] } ``` An unknown block type renders as nothing rather than an error, so a guide written against a newer version stays readable on an older one. ### Inline text Block text supports a deliberately small subset: `**bold**`, `*italic*`, `` `code` ``, `[links](https://example.com)`, and line breaks. Nothing else - no fonts, sizes or colours. Emphasis is semantic and the theme decides how it looks, which is what makes dark mode work and keeps a guide written next year matching one written today. Text is escaped **before** formatting is applied, so no markup in the source can reach the DOM. `javascript:` and `data:` links are dropped. --- ## 2. Creating a guide from a script Every endpoint is listed in `docs/api-inventory.json`; this is the workflow. ```bash TOK=$(curl -s -X POST http://localhost:5001/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"...","password":"..."}' | jq -r .data.access_token) # 1. Create - always starts as a draft curl -X POST http://localhost:5001/api/guides \ -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \ -d '{ "title": "Replace a part marker controller", "summary": "Restoring patterns and settings after a swap", "tags": "partmarker, backups", "blocks": [ {"id":"b1","type":"paragraph","text":"The new unit arrives **empty**."}, {"id":"b2","type":"callout","variant":"warning","title":"Before you start", "text":"Check the marking head."}, {"id":"b3","type":"checklist","items":[ {"id":"i1","text":"Copy the snapshot path"}, {"id":"i2","text":"Confirm the pattern count"}]}, {"id":"b4","type":"code","language":"powershell", "text":"Backup-PartMarker.ps1 -Force"} ] }' # 2. Publish - readers see nothing until this happens curl -X POST http://localhost:5001/api/guides/12/publish \ -H "Authorization: Bearer $TOK" ``` A scoped API token works the same way as a user JWT. ### What the server rejects Validation happens at the door rather than at render time, because a malformed guide stored is a page that fails every time somebody opens it, long after the client bug that caused it: - a block with no `id`, or a duplicate `id` - an unknown block `type` (the error lists the known ones) - a checklist item with no `id` - more than 500 blocks, or more than 2 MB of blocks - text longer than 20,000 characters in one block Errors name the offending block by position: `block 3: needs a stable id`. --- ## 3. Drafts, versions and publishing Three separate things, and the distinction is what stops a live procedure changing under somebody following it: | | What it is | |---|---| | **Working copy** | The `guides` row. What the editor loads and saves | | **Revisions** | Immutable snapshots in `guiderevisions`, written when content changes | | **Published pointer** | Which revision READERS get | - `PUT /api/guides/` saves the working copy and writes a revision **only if the content hash changed**. An autosave that changed nothing records nothing, so the history stays readable. The response carries `saved: false`. - The hash covers title, summary, tags **and** blocks - renaming or retagging is a change a reader notices. - `POST /api/guides//publish` **promotes an existing revision**. It does not snapshot, so a publish cannot ship unsaved edits nobody reviewed. - `POST /api/guides//restore/` brings old content back as a **new** revision. History is never rewritten. - Drafts are invisible to readers, to `GET /api/guides/` (404, not 403), and to global search. --- ## 4. Images ```bash curl -X POST http://localhost:5001/api/guides/images \ -H "Authorization: Bearer $TOK" -F file=@screenshot.png -F guideid=12 ``` Returns `{"url": "/api/guides/images/5", ...}` for use as an image block's `url`. PNG, JPEG, GIF and WebP, up to 8 MB. `guideid` is optional because an image is pasted before a new guide has an id. Bytes live in the database, not on disk: a row travels with a database backup, whereas a file under `instance/` has been lost to a container rebuild on this deployment before. **Known gap:** images uploaded to a guide that is then abandoned are never pruned. --- ## 5. Proofing The editor runs checks continuously and warns rather than blocking. They exist because none of these are spelling mistakes: - a **smart quote or long dash in a code block** - autocorrect does this, the command then fails, and it still looks right to the author (one-click fix) - an empty checklist item, which reads as a step somebody forgot to write - a heading with nothing under it, left behind by a reorganise - an image with no alt text - a leftover `TODO`, `TBD` or `FIXME` - no summary or tags, which makes a guide hard to find Publishing with unresolved errors asks for confirmation; it does not refuse. A procedure that exists imperfectly beats one nobody wrote. --- ## 6. Printing Print always renders **light**, whatever the screen theme, because a dark page lays a full sheet of toner down and knocks the text out in white. Also on paper: no buttons or progress bar, backgrounds dropped, steps and code blocks kept whole across page breaks, code wrapped rather than clipped, and checkboxes printed **empty** even where ticked on screen - a printout is a worksheet somebody ticks by hand. The footer carries the version and the print date, so a stale copy on a noticeboard is spottable. --- ## 7. Permissions | Permission | Allows | |---|---| | `guides.view` | Read guides | | `guides.create` | Create, and edit your OWN guide while it is unpublished | | `guides.edit` | Edit any guide, including published ones | | `guides.publish` | Publish or withdraw | | `guides.delete` | Delete | The split between `edit` and `publish` is deliberate: a technician can write a procedure up while somebody else decides it is fit for the floor. --- ## 8. Converting existing documents There is no importer yet. When one is written, the practical notes are: - **Structured HTML converts cleanly.** The GE Aerospace KB template's `alert alert-warning` divs map to warning callouts, `code-block` to code blocks, `step` divs to steps. - **Markdown converts well**: headings, fenced code, lists and tables all have matching block types. - **Plain-text runbooks convert poorly.** ASCII underlines and hand-indented lists carry no structure, so everything lands as paragraphs and a person has to mark up the steps afterwards. Worth converting anyway - the text is then searchable and versioned - but budget the cleanup. - Generate ids yourself and keep them stable across re-imports, or every re-import resets readers' progress. --- ## See also - `docs/api-inventory.json` - every endpoint with auth, params and an example - `plugins/guides/services/blocks.py` - the validation rules, and why each exists - `plugins/guides/services/authoring.py` - the single write path