@protoboardai/ext-docs 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +18 -0
  3. package/README.md +78 -2
  4. package/evals/build-datasheet/case.json +147 -0
  5. package/fonts/KodeMono-Bold.ttf +0 -0
  6. package/fonts/KodeMono-Medium.ttf +0 -0
  7. package/fonts/KodeMono-Regular.ttf +0 -0
  8. package/fonts/KodeMono-SemiBold.ttf +0 -0
  9. package/fonts/OFL.txt +93 -0
  10. package/lib/blocks.ts +268 -0
  11. package/lib/bom.ts +126 -0
  12. package/lib/build.ts +202 -0
  13. package/lib/charts.ts +241 -0
  14. package/lib/context.ts +122 -0
  15. package/lib/derived.ts +181 -0
  16. package/lib/docs/assembly.ts +674 -0
  17. package/lib/docs/common.ts +81 -0
  18. package/lib/docs/datasheet.ts +875 -0
  19. package/lib/docs/product.ts +45 -0
  20. package/lib/figures.ts +195 -0
  21. package/lib/format.ts +221 -0
  22. package/lib/html.ts +135 -0
  23. package/lib/model.ts +312 -0
  24. package/lib/package/assembly.ts +268 -0
  25. package/lib/package/common.ts +109 -0
  26. package/lib/package/plan.ts +432 -0
  27. package/lib/package/spec.ts +203 -0
  28. package/lib/png.ts +91 -0
  29. package/lib/render/cdp.ts +5 -0
  30. package/lib/render/page/render.html +17 -0
  31. package/lib/render/page/render.js +390 -0
  32. package/lib/render/renderer.ts +161 -0
  33. package/lib/render/server.ts +105 -0
  34. package/lib/standin.ts +36 -0
  35. package/lib/standins/multirotor.ts +283 -0
  36. package/lib/templates/doc.css +247 -0
  37. package/lib/templates/flow.js +131 -0
  38. package/lib/testdata.ts +85 -0
  39. package/lib/types.ts +120 -0
  40. package/lib/values.ts +309 -0
  41. package/lib/verify.ts +276 -0
  42. package/ops/docs.ts +52 -0
  43. package/ops/package.ts +9 -0
  44. package/package.json +62 -4
  45. package/protoboard-extension.json +94 -0
  46. package/skills/uhd-tech-docs/SKILL.md +234 -0
  47. package/skills/uhd-tech-docs/references/docspec.md +56 -0
  48. package/skills/uhd-tech-docs/references/house-style.md +50 -0
  49. package/skills/uhd-tech-docs/references/style-study.md +270 -0
  50. package/skills/uhd-tech-docs/references/test-data.md +49 -0
  51. package/skills/uhd-tech-docs/references/value-queries.md +67 -0
  52. package/skills/uhd-tech-docs/templates/test-data.example.json +33 -0
  53. package/skills/uhd-tech-docs/templates/test-data.schema.json +213 -0
  54. package/skills/uhd-tech-docs-verify/SKILL.md +104 -0
  55. package/skills/uhd-tech-docs-verify/references/visual-review.md +43 -0
@@ -0,0 +1,49 @@
1
+ # Test data
2
+
3
+ ## Convention
4
+
5
+ ```
6
+ docs/test-data/<test-id>.json one file per test; id == file name
7
+ ```
8
+
9
+ Schema: [`../templates/test-data.schema.json`](../templates/test-data.schema.json).
10
+ Example of a measured file: [`../templates/test-data.example.json`](../templates/test-data.example.json).
11
+
12
+ | Field | Meaning |
13
+ | --- | --- |
14
+ | `id`, `title` | stable test id (used by documents), human title |
15
+ | `status` | `"standin"` (derived from the model, watermarked) or `"measured"` |
16
+ | `subject` | `{ system, modules[] }` — what was tested |
17
+ | `conditions[]` | `{ name, value, unit }` — supply voltage, ambient, propeller, firmware… (`unit: ""` for text) |
18
+ | `provenance` | `method` (always); stand-in: `derivedFrom[]` (value queries), `assumptions[]`, `generator`, `generatedAt`; measured: `date`, `operator` and/or `instruments[]`, `rawFiles[]` |
19
+ | `columns[]` | `{ id, label, unit }` |
20
+ | `rows[][]` | one value per column |
21
+ | `summary[]` | `{ id, label, value, unit }` — headline figures documents quote |
22
+
23
+ ## Stand-in data
24
+
25
+ `protoboard run tech-docs` (input `standins`, default true) first writes the recipe named in
26
+ `docspec.testData.standins` (e.g. `multirotor`, the extension's `lib/standins/multirotor.ts`). A recipe derives
27
+ plausible data from model values, lists each input query in `provenance.derivedFrom`, and states
28
+ every value the model could not supply in `provenance.assumptions`. Documents draw stand-in data
29
+ with the **STAND-IN DATA — NOT MEASURED** watermark, an in-chart badge, a `stand-in` tag on the
30
+ chart title and the **S** marker on every value.
31
+
32
+ Quadcopter stand-ins (from the MEPS motor's 50 %/100 % thrust points, battery V/capacity, masses,
33
+ O4/GNSS supply figures): `throttle-sweep`, `hover-current`, `flight-time`, `thrust-to-weight`,
34
+ `esc-temp-punchout`, `battery-discharge`.
35
+
36
+ ## Dropping in real results
37
+
38
+ 1. Run the test; keep raw logs (thrust stand CSV, Blackbox, thermocouple log) next to the file or
39
+ in a data store, and list them in `provenance.rawFiles`.
40
+ 2. Write `<test-id>.json` with the **same id and column ids** as the stand-in it replaces,
41
+ `status: "measured"`, real `conditions`, and `provenance.method`, `date`, `operator` or
42
+ `instruments`. Units must match the columns.
43
+ 3. Rebuild: `npx protoboard run tech-docs`. The stand-in generator skips measured
44
+ files; charts lose the watermark, values get the **M** marker.
45
+ 4. Verify: `npx protoboard run tech-docs-verify` fails if a measured chart still carries
46
+ a watermark, or a stand-in chart lacks one, or a file is malformed.
47
+
48
+ To add a *new* test, give it a new id and add a chart or table for it in the builder (or a
49
+ test-report document); a file no document reads is still validated.
@@ -0,0 +1,67 @@
1
+ # Value queries
2
+
3
+ Implementation: `lib/values.ts` (resolver), `format.ts` (formatting),
4
+ `derived.ts` (derivations). The same resolver runs at build and verify time.
5
+
6
+ ## Forms
7
+
8
+ ```
9
+ def:<moduleId>:<path> field of a module definition (lookup(moduleId))
10
+ inst:<instance path>:<path> same, by instance path (stack/fc, arm_fl/motor, gnss)
11
+ sys:<path> system results
12
+ system the root ModuleDef (links, description…)
13
+ checks checkSystem(SYSTEM): links[], diagnostics[]
14
+ bom docs/<system-id>-bom.json rows (as published)
15
+ wiring wiringChecklist incl. sub-assemblies
16
+ scenarios [{id,label,description,diagnostics}] from scenarios.ts
17
+ assembly {mates, placements, hardware (counts), issues}
18
+ derived:<name> named derivation
19
+ derived:<fn>(a,b) parameterised derivation
20
+ ver:<partId>:<path> the part's verification.json (parts/evidence/<id>/, or vendored with its catalog revision)
21
+ test:<test-id>:<path> docs/test-data/<test-id>.json
22
+ ```
23
+
24
+ Paths: `.key`, `[n]`, `[key=value]` (first element whose key equals value), `.length`.
25
+
26
+ ```
27
+ def:dolphinrc-f405-v3-flight-controller:interfaces[id=bec_5v].parameters[id=max_current].value
28
+ def:dji-o4-air-unit:traits[type=operating_conditions].params.operating_temperature_C
29
+ def:meps-neon-2207-v2-1950kv:traits[type=performance].params.thrust_tests[0].rows[throttle_pct=100].thrust_g
30
+ sys:checks.diagnostics[id=supply_budget:stack:bec_10v].details.loadW
31
+ sys:system.links[id=stack_mount].mate.gapMm
32
+ test:throttle-sweep:rows[3][4] (unit = columns[4].unit)
33
+ ```
34
+
35
+ A parameter stated as a `range` resolves `…value` to the range (`[6, 30]` → `6.0–30.0 V`).
36
+
37
+ ## Units
38
+
39
+ - `parameters[…].value` takes the parameter's `unit`;
40
+ - metadata keys carry units by suffix: `_mm _g _V _A _mA _W _Wh _mAh _ms _s _C _Hz _deg _in _rpm`
41
+ (`weight_g` → g, `operating_temperature_C` → °C);
42
+ - otherwise pass `u=<unit>` in the format.
43
+
44
+ ## Status
45
+
46
+ | Status | When | Marker |
47
+ | --- | --- | --- |
48
+ | `source` | a library part with `sources.json` / `verification.json` | none |
49
+ | `model` | a custom module's design value, a UHD check result, a BOM row | none |
50
+ | `assumption` | the module has an `assumption` trait whose `field` names the value | A |
51
+ | `derived` | `derived:` queries (formula + input queries recorded) | D |
52
+ | `standin` | `test:` query on a stand-in file, or a derivation with a stand-in input | S |
53
+ | `measured` | `test:` query on a measured file | M |
54
+ | `gap` | the path resolves to nothing: rendered "—" | — |
55
+
56
+ ## Adding a derivation
57
+
58
+ ```ts
59
+ // lib/derived.ts
60
+ "power.hover_margin": {
61
+ label: "…", unit: "%", formula: "human-readable formula",
62
+ compute: (ctx) => ({ value, inputs: ["def:…", "sys:…"], note: "…" }),
63
+ },
64
+ ```
65
+
66
+ Find modules by what they are (`category(def)`), not by id, so the derivation works for other
67
+ systems. Never compute a shown value inside a builder: put it here so the verifier recomputes it.
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "uhd-test-data/1",
3
+ "id": "hover-current",
4
+ "title": "Hover current and all-up-weight sensitivity",
5
+ "status": "measured",
6
+ "subject": { "system": "quadcopter-5in", "modules": ["meps-neon-2207-v2-1950kv", "cnhl-black-series-1100mah-6s-100c"] },
7
+ "conditions": [
8
+ { "name": "Battery nominal", "value": 22.2, "unit": "V" },
9
+ { "name": "All-up weight (weighed)", "value": 0, "unit": "g" },
10
+ { "name": "Ambient", "value": 0, "unit": "°C" },
11
+ { "name": "Firmware", "value": "Betaflight 2025.12.x", "unit": "" }
12
+ ],
13
+ "provenance": {
14
+ "method": "EXAMPLE ONLY — replace every 0 with a measurement. Hover in still air for 60 s per ballast step; current from the FC current sensor (calibrated against a clamp meter), averaged over the steady 40 s.",
15
+ "date": "YYYY-MM-DD",
16
+ "operator": "name",
17
+ "instruments": ["DolphinRC F405 V3 current sensor (Blackbox)", "clamp meter model"],
18
+ "rawFiles": ["blackbox/LOG00001.BFL"]
19
+ },
20
+ "columns": [
21
+ { "id": "auw", "label": "All-up weight", "unit": "g" },
22
+ { "id": "thrust_motor", "label": "Hover thrust per motor", "unit": "g" },
23
+ { "id": "current_total", "label": "Hover current", "unit": "A" },
24
+ { "id": "power_total", "label": "Hover power", "unit": "W" }
25
+ ],
26
+ "rows": [[0, 0, 0, 0]],
27
+ "summary": [
28
+ { "id": "auw_g", "label": "All-up weight", "value": 0, "unit": "g" },
29
+ { "id": "hover_current_A", "label": "Hover current", "value": 0, "unit": "A" },
30
+ { "id": "hover_power_W", "label": "Hover power", "value": 0, "unit": "W" },
31
+ { "id": "avionics_current_A", "label": "Avionics current", "value": 0, "unit": "A" }
32
+ ]
33
+ }
@@ -0,0 +1,213 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "uhd-test-data/1",
4
+ "title": "UHD document test data",
5
+ "description": "One test per file: docs/test-data/<id>.json in the project. status standin = derived from the model (watermarked); measured = real results.",
6
+ "type": "object",
7
+ "required": [
8
+ "id",
9
+ "title",
10
+ "status",
11
+ "subject",
12
+ "conditions",
13
+ "provenance",
14
+ "columns",
15
+ "rows",
16
+ "summary"
17
+ ],
18
+ "properties": {
19
+ "$schema": {
20
+ "type": "string"
21
+ },
22
+ "id": {
23
+ "type": "string",
24
+ "pattern": "^[a-z0-9][a-z0-9-]*$",
25
+ "description": "Equals the file name without .json"
26
+ },
27
+ "title": {
28
+ "type": "string"
29
+ },
30
+ "status": {
31
+ "enum": [
32
+ "standin",
33
+ "measured"
34
+ ]
35
+ },
36
+ "subject": {
37
+ "type": "object",
38
+ "required": [
39
+ "system"
40
+ ],
41
+ "properties": {
42
+ "system": {
43
+ "type": "string"
44
+ },
45
+ "modules": {
46
+ "type": "array",
47
+ "items": {
48
+ "type": "string"
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "conditions": {
54
+ "type": "array",
55
+ "items": {
56
+ "type": "object",
57
+ "required": [
58
+ "name",
59
+ "value",
60
+ "unit"
61
+ ],
62
+ "properties": {
63
+ "name": {
64
+ "type": "string"
65
+ },
66
+ "value": {
67
+ "type": [
68
+ "number",
69
+ "string"
70
+ ]
71
+ },
72
+ "unit": {
73
+ "type": "string"
74
+ }
75
+ }
76
+ }
77
+ },
78
+ "provenance": {
79
+ "type": "object",
80
+ "required": [
81
+ "method"
82
+ ],
83
+ "properties": {
84
+ "method": {
85
+ "type": "string"
86
+ },
87
+ "derivedFrom": {
88
+ "type": "array",
89
+ "items": {
90
+ "type": "string"
91
+ },
92
+ "description": "Value queries (@protoboardai/ext-docs lib/values.ts) a stand-in was derived from"
93
+ },
94
+ "assumptions": {
95
+ "type": "array",
96
+ "items": {
97
+ "type": "string"
98
+ }
99
+ },
100
+ "generator": {
101
+ "type": "string"
102
+ },
103
+ "generatedAt": {
104
+ "type": "string"
105
+ },
106
+ "operator": {
107
+ "type": "string"
108
+ },
109
+ "date": {
110
+ "type": "string",
111
+ "description": "Measured data: ISO date of the test"
112
+ },
113
+ "instruments": {
114
+ "type": "array",
115
+ "items": {
116
+ "type": "string"
117
+ }
118
+ },
119
+ "rawFiles": {
120
+ "type": "array",
121
+ "items": {
122
+ "type": "string"
123
+ }
124
+ }
125
+ }
126
+ },
127
+ "columns": {
128
+ "type": "array",
129
+ "minItems": 1,
130
+ "items": {
131
+ "type": "object",
132
+ "required": [
133
+ "id",
134
+ "label",
135
+ "unit"
136
+ ],
137
+ "properties": {
138
+ "id": {
139
+ "type": "string"
140
+ },
141
+ "label": {
142
+ "type": "string"
143
+ },
144
+ "unit": {
145
+ "type": "string"
146
+ }
147
+ }
148
+ }
149
+ },
150
+ "rows": {
151
+ "type": "array",
152
+ "items": {
153
+ "type": "array",
154
+ "items": {
155
+ "type": [
156
+ "number",
157
+ "string"
158
+ ]
159
+ }
160
+ }
161
+ },
162
+ "summary": {
163
+ "type": "array",
164
+ "items": {
165
+ "type": "object",
166
+ "required": [
167
+ "id",
168
+ "label",
169
+ "value",
170
+ "unit"
171
+ ],
172
+ "properties": {
173
+ "id": {
174
+ "type": "string"
175
+ },
176
+ "label": {
177
+ "type": "string"
178
+ },
179
+ "value": {
180
+ "type": [
181
+ "number",
182
+ "string"
183
+ ]
184
+ },
185
+ "unit": {
186
+ "type": "string"
187
+ }
188
+ }
189
+ }
190
+ }
191
+ },
192
+ "allOf": [
193
+ {
194
+ "if": {
195
+ "properties": {
196
+ "status": {
197
+ "const": "measured"
198
+ }
199
+ }
200
+ },
201
+ "then": {
202
+ "properties": {
203
+ "provenance": {
204
+ "required": [
205
+ "method",
206
+ "date"
207
+ ]
208
+ }
209
+ }
210
+ }
211
+ }
212
+ ]
213
+ }
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: uhd-tech-docs-verify
3
+ description: Verify UHD technical documents (datasheet, assembly guide) against the live model — re-resolve every bound value, check units/format, placeholders, stand-in watermarks, images, pagination and overflow, typography, the BOM file against the model, and assembly coverage of every mate, hardware placement and wiring link (protoboard run tech-docs-verify) — then run a visual review of the page renders. Use after uhd-tech-docs builds, and before handing documents to anyone.
4
+ ---
5
+
6
+ # UHD technical documents — verify
7
+
8
+ Two stages: the automated verifier, then a visual review of every page. Loop with
9
+ **uhd-tech-docs** until both pass.
10
+
11
+ ## Purpose
12
+
13
+ Prove that every value in the documents is what the live model says, that
14
+ the layout is clean, and that every mate, hardware placement and wiring link
15
+ is in an assembly step. Use it after every **uhd-tech-docs** build, before
16
+ handing documents to anyone, and before a release packages them. In
17
+ **build-a-project** it closes stage 10.
18
+
19
+ ## Inputs
20
+
21
+ | Input | Where | From |
22
+ | --- | --- | --- |
23
+ | Built documents | `docs/<system-id>-*.html`, `*.pdf`, `*.values.json`, `docs/<system-id>-bom.json` | **uhd-tech-docs** |
24
+ | The live model | the project | the project |
25
+ | Tools | `npx protoboard doctor`: `chrome`, `poppler` ✓ | Google Chrome, poppler |
26
+ | The visual checklist | [visual-review](references/visual-review.md) | this skill |
27
+
28
+ ## Steps
29
+
30
+ ### 1. Stage 1: automated
31
+
32
+ ```bash
33
+ npx protoboard run tech-docs-verify # all documents
34
+ npx protoboard run tech-docs-verify --input only=assembly --format json
35
+ ```
36
+
37
+ It loads each paginated HTML in headless Chrome, re-loads the project, and writes
38
+ `docs/.review/verify-report.json` plus fresh page PNGs (`docs/.review/<doc>/page-NN.png`, from the
39
+ PDF with pdftoppm). The run fails on any error; its record lists every finding.
40
+
41
+ | Check | Fails when |
42
+ | --- | --- |
43
+ | values | a `[data-q]` element's text differs from the query re-resolved and re-formatted with its `data-f`, or its status changed (model edited since the build: rebuild) |
44
+ | format | a numeric value lacks the space before its unit; a unit is spelled two ways (warning) |
45
+ | placeholders | visible text contains NaN, undefined, null, `[object Object]`, TODO, TBD, `{{ }}`, Infinity |
46
+ | standin | a stand-in value lacks its S marker; a chart drawn from stand-in data lacks the watermark, or a measured chart still has one; a chart names a missing test file; the legend is missing |
47
+ | images | an `<img>` is missing, fails to decode, or is blank (ink < 0.4 %, flat luminance) |
48
+ | layout | a page body overflows; a table/figure/list is wider than the body; a heading or keep-with-next block is last on a page; a table continuation has < 2 rows; PDF page count ≠ HTML pages; a PDF page is blank |
49
+ | typography | text in a font other than Kode Mono / Kode Mono Bold, fonts not loaded or not embedded, text below 5 pt, more than 16 sizes (warning), system-font fallback in the PDF (warning) |
50
+ | bom | a line of `docs/<id>-bom.json` is missing from the datasheet, the line count differs, or the file is stale against `buildBom()` of the live model |
51
+ | coverage | a mate from `assemble().mates`, a hardware placement from `assemble().hardware`, or a wiring link is in no step (steps record them in `data-mates / data-hardware / data-wiring`); a step lists hardware the model no longer places; automatic steps remain (warning) |
52
+
53
+ Fix the cause, not the symptom: a value mismatch means rebuild (or a builder computing a value
54
+ outside `values.ts`); a stale BOM means rebuild the documents; coverage gaps mean the
55
+ doc spec's step patterns miss something.
56
+
57
+ ### 2. Stage 2: visual review
58
+
59
+ Open every PNG in `docs/.review/<doc>/` (an agent can read them as images; use a higher
60
+ `pdftoppm -r 110` render for detail) and walk [the checklist](references/visual-review.md).
61
+ Record findings as `{ doc, page, issue, fix }`, fix them in the builder / CSS / doc spec, rebuild,
62
+ re-verify, and look again. Do not accept a page that only passes the automated checks.
63
+
64
+ ### 3. Report
65
+
66
+ Summarise: errors fixed, remaining warnings with reasons, the model gaps the documents expose
67
+ (gap markers "—", stand-in assumptions, missing torque/mass/direction data) for the model author.
68
+
69
+ ## Decision points
70
+
71
+ | Decision | Rule |
72
+ | --- | --- |
73
+ | A value mismatch | Rebuild first; if it persists, a builder computes a value outside `values.ts`: move it into a query or a derivation. |
74
+ | A warning | Fix it, or state the reason in the report (a unit spelled two ways on purpose is rare). |
75
+ | A page that passes the checks but looks wrong | It fails the review; fix and rebuild. |
76
+ | Model gaps | Reported to the model author; never filled in the document. |
77
+
78
+ ## Outputs
79
+
80
+ | Output | Where |
81
+ | --- | --- |
82
+ | The verifier's report | `docs/.review/verify-report.json` |
83
+ | Page renders | `docs/.review/<doc>/page-NN.png` |
84
+ | The review summary | the report back to the author (and `docs/reviews/` when the project keeps one) |
85
+
86
+ ## Verification
87
+
88
+ `npx protoboard run tech-docs-verify` succeeds (exit 0, no errors in the
89
+ report), and every page PNG has been looked at with the checklist, with each
90
+ finding fixed or recorded with its reason.
91
+
92
+ ## Failure handling
93
+
94
+ | Code or symptom | Means | Do |
95
+ | --- | --- | --- |
96
+ | values check fails | The model changed since the build | `npx protoboard run tech-docs`, then verify again |
97
+ | bom check fails (stale) | The BOM file is older than the model | Rebuild the documents |
98
+ | standin check fails | A watermark or S marker is missing, or a measured chart still has one | Rebuild; check the test-data file's `status` |
99
+ | coverage check fails | A mate, hardware placement or wiring link is in no step | Add it to `assembly.steps` in the doc spec |
100
+ | typography warnings: system-font fallback | A glyph Kode Mono lacks, or `assets/fonts` missing | Rebuild with `run tech-docs`; replace the glyph |
101
+ | `run` refused: tool `chrome` or `poppler` missing | No renderer | Install them, or set `$CHROME` |
102
+ | images check: blank figure | A body without CAD, or a camera that frames nothing | Check the part's artifact (`PB_ARTIFACT_UNAVAILABLE` is expected to show "not available", not blank) and the step's camera |
103
+
104
+ Every other code: **system-compose** [diagnostic codes](../system-compose/references/diagnostic-codes.md).
@@ -0,0 +1,43 @@
1
+ # Visual review checklist
2
+
3
+ For each page PNG in `docs/.review/<doc>/`. Mark each item pass / fix.
4
+
5
+ ## Every page
6
+
7
+ - [ ] Header band and footer present (not on the cover); page number `NN / NN` matches the
8
+ contents table.
9
+ - [ ] Nothing cut at the body edge or bottom; no half-empty page caused by a block that could have
10
+ split (whitespace is fine when a section starts a new page).
11
+ - [ ] One left edge: headings, rules, tables and text align; figures centred or full width.
12
+ - [ ] No text overlapping art, other text, bubbles or leaders; no clipped labels in SVGs.
13
+ - [ ] Type: only the house sizes; no bold/italic fallbacks; no tofu boxes or odd glyphs (µ, Ø, ×, →).
14
+ - [ ] Accent used for highlights only (not body text); red only for warnings/errors.
15
+
16
+ ## Figures (3D)
17
+
18
+ - [ ] The part(s) of the step are obvious: new parts in dark lines, hardware in accent, context grey.
19
+ - [ ] Exploded parts follow their axis in stacking order and do not collide or hide each other;
20
+ dashed axes visible through the holes.
21
+ - [ ] Camera matches the previous step unless the step needs another view; nothing important is
22
+ behind a context part.
23
+ - [ ] Callout bubbles sit outside the silhouette, leaders do not cross each other or pass through
24
+ other callouts, every bubble matches a letter in the parts strip or a number in the table.
25
+ - [ ] Line weights consistent between figures (similar output width); no aliasing or noise.
26
+ - [ ] Dimensions point at the right features, text readable and bound to a value.
27
+
28
+ ## Tables and charts
29
+
30
+ - [ ] Header row on tint, numbers right aligned, units consistent, no wrapped one-word columns.
31
+ - [ ] Continued tables repeat their header; no group row orphaned at a page end.
32
+ - [ ] Charts: axes titled with units, ticks readable, legend or labels not overlapping the data.
33
+ - [ ] Every chart from stand-in data shows the watermark and the badge; measured charts show none.
34
+
35
+ ## Content sense
36
+
37
+ - [ ] Values are plausible (orders of magnitude, units) and identical where repeated.
38
+ - [ ] Assembly steps are in a buildable order, actions are one operation each, quantities match
39
+ the strip, hardware counts match the hardware page and the coverage appendix.
40
+ - [ ] Wiring tables read pad-to-pad in the right direction; crossovers flagged.
41
+ - [ ] Gap markers (—) and assumptions (A) are visible where the model lacks data, not papered over.
42
+ - [ ] Safety notes present where the model or the doc spec calls for them (props, battery,
43
+ cooling).