|
| 1 | +--- |
| 2 | +"@objectstack/spec": patch |
| 3 | +--- |
| 4 | + |
| 5 | +fix(spec): a reference page's opening paragraph is the module's own doc block, never a symbol's (#5059) |
| 6 | + |
| 7 | +`getFileDescription()` took the **first** doc block anywhere in a `*.zod.ts` |
| 8 | +file, verbatim, and published it as the page's opening paragraph. That is not a |
| 9 | +rule about descriptions — it is a rule about *ordering*: whichever declaration |
| 10 | +happened to sit at the top of the file donated its comment to a public |
| 11 | +document. Adding a helper above the first schema silently rewrote a published |
| 12 | +page, and no gate could see it. `check:docs` compares the generated page against |
| 13 | +the source and the page reproduced the wrong block faithfully, so there was no |
| 14 | +drift to report; the trap was written down when the generator was built and |
| 15 | +still landed on `main` twice. |
| 16 | + |
| 17 | +The measured victim surface was **six pages**. The Translation protocol |
| 18 | +reference opened with `Shared history sentence for every shape in this file |
| 19 | +(#4001).` — this repo's internal tightening-campaign narrative — and the Mapping |
| 20 | +page with its sibling. Four more had no history constant anywhere near them: |
| 21 | +`api/contract` published the doc of `ApiErrorSchema.code` (a comment *nested |
| 22 | +inside* an object literal), `api/realtime` published `Transport Protocol Enum` |
| 23 | +on a page documenting fourteen schemas, and `api/protocol` / `kernel/plugin` the |
| 24 | +same shape. Any future "move a helper to the top of the file" makes another. |
| 25 | + |
| 26 | +The selection now follows **TSDoc's own rule, read back**: a doc block belongs |
| 27 | +to the declaration it immediately precedes — which is exactly the text an editor |
| 28 | +shows when you hover that symbol. So a module description must be a block that |
| 29 | + |
| 30 | +- starts at column 0 (a block indented inside a declaration body documents a |
| 31 | + property, never a module), |
| 32 | +- appears before the first declaration (imports and re-exports introduce no |
| 33 | + symbol of their own and do not close the header zone), and |
| 34 | +- is not immediately followed by a declaration. |
| 35 | + |
| 36 | +When no block qualifies the page prints no description at all — 宁可缺,不要错. |
| 37 | +A confidently rendered internal note is a page that lies about its subject, |
| 38 | +which is worse for a reader (and for an AI author working from these pages) than |
| 39 | +a page that opens with its `Source:` pointer. |
| 40 | + |
| 41 | +**Twenty reference pages lost an opening paragraph**, each of which was a |
| 42 | +symbol's JSDoc rather than the module's: the six above plus `ai/solution-blueprint`, |
| 43 | +`ai/tool`, `api/error-code-ledger`, `api/router`, `automation/approval`, |
| 44 | +`cloud/template-manifest`, `data/driver-mysql`, `data/driver-postgres`, |
| 45 | +`data/driver-sqlite`, `kernel/manifest`, `shared/enums`, `system/doc`, |
| 46 | +`system/notification`, `ui/responsive`. **No page lost a real module header** — |
| 47 | +the other 178 pages with a `Source:` line keep their description byte for byte. |
| 48 | +A module that wants its opening paragraph back writes one block that documents |
| 49 | +no symbol; 178 sources already do. |
| 50 | + |
| 51 | +The rule is also the gate. The issue proposed failing on first sentences |
| 52 | +matching `#\d{3,}` / `Shared history`, but that recognises only the |
| 53 | +history-constant subclass, and only after publication — it would have caught two |
| 54 | +of the six. A selection rule that cannot pick a symbol's comment makes the whole |
| 55 | +class impossible instead. `scripts/lib/file-description.ts` (extracted from |
| 56 | +`build-docs.ts`, following `format-type.ts` #4912 and `escape-mdx.ts` #5452) and |
| 57 | +its pin suite `scripts/file-description.test.ts` carry it, corpus check included. |
0 commit comments