Observation-class finding, surfaced while landing the view name-grammar note (PR #13262). Filed unassigned; not fixed there — that PR's ruling authorized a family-level note on one module, not a sweep.
The residual hole in the module-description selection rule
packages/spec/scripts/lib/file-description.ts decides which doc block of a *.zod.ts module becomes the prose its reference page opens with. Its rule, quoted from its own header, is three conditions: top-level, in the header zone, and documenting nothing — where "documenting nothing" means the block is not immediately followed by a declaration, and imports / re-exports explicitly do not count as declarations (MODULE_PLUMBING), because this repo writes module headers on either side of them.
That third condition is the load-bearing one, and its own justification is TSDoc's rule read back: a doc block belongs to the declaration it immediately precedes. But a doc block immediately preceding an import or export { … } line also belongs to something — the symbol being imported or re-exported. The rule cannot tell those two apart, so a comment written about a re-exported symbol is published as the page's subject.
Measured instance, confirmed end to end: packages/spec/src/ui/view.zod.ts carried this block at line 18, directly above import { HttpMethodSubsetSchema, HttpRequestSchema } from '../shared/http.zod';
HTTP Method Enum & HTTP Request Schema
Migrated to shared/http.zod.ts. Re-exported here for backward compatibility.
content/docs/references/ui/view.mdx — a page documenting ~50 view schemas — opened with exactly that, and gen:skill-refs copied its first line into two published skill indexes, so skills/objectstack-data/references/_index.md and skills/objectstack-ui/references/_index.md each described ui/view.zod.ts as "HTTP Method Enum & HTTP Request Schema". PR #13262 corrects that one module by giving it a real module description; it does not touch the rule or the other modules.
Scale
Reimplementing the selection rule and running it over packages/spec/src/*/*.zod.ts on origin/main at 7404925:
| bucket |
count |
| sources scanned |
193 |
| block selected, sits above a real declaration boundary (ordinary module header) |
105 |
block selected, sits directly above an import / re-export line |
69 |
| no qualifying block — page prints no description |
19 |
The 69 are not 69 defects: most are genuine module headers deliberately written above the import block, which is the spelling MODULE_PLUMBING exists to permit. The suspicious subset is the one where the block sits inside the import list rather than at the top of the file — 29 of the 69 begin after line 5, and 22 of those 29 open with a title-case symbol name, the signature of a comment written for the symbol on the next line:
ui/action.zod.ts:27 "Action Parameter Schema"
ui/app.zod.ts:10 "Base Navigation Item Schema"
ui/component.zod.ts:213 "Empty Properties Schema"
ui/dashboard.zod.ts:28 "Color variant for dashboard widgets (e.g., KPI cards)."
ui/page.zod.ts:22 "Page Region Schema"
ui/report.zod.ts:11 "Report Type Enum"
data/field.zod.ts:35 "Field Type Enum"
data/object.zod.ts:9 "API Operations Enum"
api/discovery.zod.ts:10 "Service Status Enum"
shared/metadata-types.zod.ts:16 " Supported metadata file formats"
…
Each of those is one published reference page whose opening paragraph may be one schema's doc rather than the module's — the same shape file-description.ts's header records as already-fixed ("Transport Protocol Enum (one enum's doc, on a page documenting fourteen schemas)"), reached through the one door the rule still leaves open. The count is a candidate list, not a verdict: each needs reading before it is called wrong, because a short title-case line can legitimately be a module header too.
Why it is worth recording
The blast radius is larger than a docs page. gen:skill-refs takes the block's first line as the pointer-row description in the published skill bundles, so a misattributed block ships to customer projects, not only to the docs site. And the failure is invisible to check:docs and check:skill-refs by construction: both compare the artifact to the source, and the artifact faithfully reproduces whatever block the rule selected — which is precisely the argument file-description.ts makes for why the selection rule, not a detector, had to be the gate.
Candidate directions (not a recommendation — this needs a spec-seat call)
- Tighten the rule: treat a block whose next non-blank line is an
import / re-export as documenting that, unless it is the file's first block (position before any import). Cheap, mechanical, and it would have caught view.zod.ts; it would also demote the ~40 legitimate headers written above the imports, so it needs the "first block" carve-out measured rather than assumed.
- Require an explicit marker (
@module <category>/<name>, which 10 sources already use) and select only a marked block. Loud and unambiguous; costs a one-line edit on ~180 sources.
- Triage the 29 by hand and give each a real module header, leaving the rule alone — the same remedy the earlier "write real module headers" pass took, applied to the subclass it did not cover.
No option is free of the others' cost, and which one is right depends on whether the rule or the corpus is judged to be the thing that is wrong.
Observation-class finding, surfaced while landing the view name-grammar note (PR #13262). Filed unassigned; not fixed there — that PR's ruling authorized a family-level note on one module, not a sweep.
The residual hole in the module-description selection rule
packages/spec/scripts/lib/file-description.tsdecides which doc block of a*.zod.tsmodule becomes the prose its reference page opens with. Its rule, quoted from its own header, is three conditions: top-level, in the header zone, and documenting nothing — where "documenting nothing" means the block is not immediately followed by a declaration, and imports / re-exports explicitly do not count as declarations (MODULE_PLUMBING), because this repo writes module headers on either side of them.That third condition is the load-bearing one, and its own justification is TSDoc's rule read back: a doc block belongs to the declaration it immediately precedes. But a doc block immediately preceding an
importorexport { … }line also belongs to something — the symbol being imported or re-exported. The rule cannot tell those two apart, so a comment written about a re-exported symbol is published as the page's subject.Measured instance, confirmed end to end:
packages/spec/src/ui/view.zod.tscarried this block at line 18, directly aboveimport { HttpMethodSubsetSchema, HttpRequestSchema } from '../shared/http.zod';content/docs/references/ui/view.mdx— a page documenting ~50 view schemas — opened with exactly that, andgen:skill-refscopied its first line into two published skill indexes, soskills/objectstack-data/references/_index.mdandskills/objectstack-ui/references/_index.mdeach describedui/view.zod.tsas "HTTP Method Enum & HTTP Request Schema". PR #13262 corrects that one module by giving it a real module description; it does not touch the rule or the other modules.Scale
Reimplementing the selection rule and running it over
packages/spec/src/*/*.zod.tsonorigin/mainat7404925:import/ re-export lineThe 69 are not 69 defects: most are genuine module headers deliberately written above the import block, which is the spelling
MODULE_PLUMBINGexists to permit. The suspicious subset is the one where the block sits inside the import list rather than at the top of the file — 29 of the 69 begin after line 5, and 22 of those 29 open with a title-case symbol name, the signature of a comment written for the symbol on the next line:Each of those is one published reference page whose opening paragraph may be one schema's doc rather than the module's — the same shape
file-description.ts's header records as already-fixed ("Transport Protocol Enum(one enum's doc, on a page documenting fourteen schemas)"), reached through the one door the rule still leaves open. The count is a candidate list, not a verdict: each needs reading before it is called wrong, because a short title-case line can legitimately be a module header too.Why it is worth recording
The blast radius is larger than a docs page.
gen:skill-refstakes the block's first line as the pointer-row description in the published skill bundles, so a misattributed block ships to customer projects, not only to the docs site. And the failure is invisible tocheck:docsandcheck:skill-refsby construction: both compare the artifact to the source, and the artifact faithfully reproduces whatever block the rule selected — which is precisely the argumentfile-description.tsmakes for why the selection rule, not a detector, had to be the gate.Candidate directions (not a recommendation — this needs a spec-seat call)
import/ re-export as documenting that, unless it is the file's first block (position before any import). Cheap, mechanical, and it would have caughtview.zod.ts; it would also demote the ~40 legitimate headers written above the imports, so it needs the "first block" carve-out measured rather than assumed.@module <category>/<name>, which 10 sources already use) and select only a marked block. Loud and unambiguous; costs a one-line edit on ~180 sources.No option is free of the others' cost, and which one is right depends on whether the rule or the corpus is judged to be the thing that is wrong.