Skip to content

feat(cli): check each authored doc against its published authoring type - #7664

Merged
josephfarina merged 3 commits into
mainfrom
josephfarina/doctor-doc-types
Oct 19, 2026
Merged

josephfarina merged 3 commits into
mainfrom
josephfarina/doctor-doc-types

Conversation

@josephfarina

@josephfarina josephfarina commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

User need

Every doc an integration ships is meant to be written to a type @astryxdesign/cli/authoring publishes: ComponentDoc, TemplateDoc, ReferenceDoc, ThemeDoc and the translation types. The CLI's loader accepts a looser shape than those types, so a doc can pass every check and still fail a type check in its own repository. Today these all pass astryx doctor integration validate:

  • a component doc without displayName or usage, or with malformed usage, theming, playground or example entries;
  • an unknown field in a component doc, such as keyword: for keywords:;
  • type: 'generic' in a doc beside Foo.tsx;
  • a template without displayName, or a block without aspectRatio;
  • a translation (docsZh, docsDense, <topic>.doc.<lang>.mjs) with fields its type doesn't have.

Authors find these only when someone runs tsc, if ever, and readers get docs the CLI then reads partly or not at all.

Problem and solution fit

This PR adds the check; #7673, stacked on it, runs it in astryx doctor integration validate and adds the repository test over Core's own docs. The two were one PR until a thorough fix took it past 2,000 lines; they split where the check meets the command.

checkDocTypes checks each doc file's value (the export its root reads) the way tsc checks a value written with the published type for its kind, using the project's own typescript:

  • the components root against ComponentDoc (read as TypeScript reads the union; messages name the member the doc's shape selects);
  • translations against ComponentTranslationDoc (and HookTranslationDoc); a translation that carries props overlays the base doc, so it is checked against the full type with no field required but each prop's and sub-component entry's name;
  • the templates root against the new IntegrationTemplateDoc (TemplateDoc for Core's own templates); the docs root against the type a doc's type names (ReferenceDoc when it names none), and topic overlays against ReferenceTranslationDoc; the themes root against ThemeDoc.

Each finding is a warning naming the doc, the line, the field path (for example usage.bestPractices[1].guidance), the problem (unknown_field, missing_field or wrong_type), TypeScript's message, and a fix: rename to the field TypeScript suggests, remove, add, or change to a value of type X. TypeScript stops at an object's first unknown field, and a value outside its type, or a nested object missing a field, hides the rest of the doc. So the check looks again, with each such field or object set aside, until nothing new appears: one run reports everything. Without TypeScript, or with one older than the docs need, nothing is checked and the report says why; with a TypeScript that has no JavaScript compiler API to read types with (TypeScript 7), the reason names the installed version instead of asking for TypeScript. With no doc files, the report says so, TypeScript or not.

Current specification

Implements architecture:cli-surface INV34, "Every descriptor satisfies its published authoring type" (AST-039 FR13, which now points to it; DEC-5), with #7673. Its evidence row names the doc-types, doctor-integration and core-doc-types tests.

Public delta and caller burden

  • Published types are added; none changes. Three new types describe an integration's templates: IntegrationPageTemplateDoc and IntegrationBlockTemplateDoc (each TemplateDoc member, with category the package's own string) and their union, IntegrationTemplateDoc. @astryxdesign/cli/authoring exports the union; @astryxdesign/cli/doc and @astryxdesign/cli/template export all three. TemplateDoc.category is the closed TemplateCategory union, which no integration template can meet, because bare astryx template groups templates by category and the loader always accepted any string. TemplateDoc stays exactly as it is, so code that reads category as a TemplateCategory keeps compiling. The check holds an integration's templates to IntegrationTemplateDoc, and Core's own to TemplateDoc. displayName and aspectRatio stay required in both, as the template reference already says. The new type has its own section in astryx docs authoring.
  • No CLI output changes in this PR: feat(cli): doctor integration validate checks each doc against its published authoring type #7673 adds the validate section and data.docTypes.
  • No new command, flag, option or environment variable.

Design and alternatives

  • The real type checker, not a second schema. The check wraps each doc's value in a satisfies check of its published type in memory and asks the project's compiler, so it can never disagree with tsc and follows the types as they change. A doc's own annotation is set aside for the check, so each doc is checked once, against the type for its root: a JSDoc @type, and in a .doc.ts doc a variable's annotation (const docs: ReferenceDoc = …), a type assertion (<ReferenceDoc>{…}), as or satisfies.
  • The type comes from the root, not the doc's own label. That is what catches type: 'generic' beside Foo.tsx: the fix says to set type: 'component' or move the doc to the docs root.
  • Set aside, then look again. An unknown field is removed, and a value outside its type, or a nested object missing a field, is cast for the next pass; the doc's own type is never cast, because it picks the type. A deeper type sets aside the object that holds it. Messages from later passes say "this value" instead of quoting the edited text.
  • A separate integration type, not a wider TemplateDoc, and not a warning. The loader and astryx template already treat any string as valid, so a warning would be about a gap in the type, not in the doc. Widening TemplateDoc.category would break code that reads it as a TemplateCategory, which can't ship in a patch. An added type changes nothing that compiles today.
  • Rejected: checking with the CLI's own zod schemas (they are deliberately looser, so old docs keep loading), and a flag or environment variable to turn it on.

Evidence

  • foundation/discovery/doc-types.test.mjs (20 tests): each problem kind; a package with no doc files, with and without TypeScript; a TypeScript 7-style install with no compiler API (one that exports none, one that can't be loaded); a TypeScript older than 4.9 with only .ts docs; a doc the compiler cannot parse; a value outside its type that hid other fields; a wrong type below the top level; a missing top-level field that nested missing fields hid, with no edited text quoted; eight unknown fields in one doc; a field reached only after an unknown one; a doc in another kind's root; an annotation naming another type, in JSDoc and in a .doc.ts doc (a variable's annotation and a type assertion); translations, including ones that carry props, whose prop and sub-component entries need only their name; a doc that re-exports its value; template, topic, overlay and theme, with an integration template's own category accepted and Core's held to TemplateCategory; no TypeScript installed.
  • On Core (run by feat(cli): doctor integration validate checks each doc against its published authoring type #7673's test): 1,502 doc values in 1,111 files checked in about 3.5 s. Its component, hook, template, theme and topic docs all pass.

Since the first automated review

  • A value outside its type no longer hides the doc's other findings: it is set aside like an unknown field, and the doc is checked again.
  • Passes go on until nothing new appears, and each sets aside at least one more field. As a guard, a doc that still finds new problems after 50 passes is listed in unchecked as not fully checked.
  • The TypeScript floor applies to every doc: 4.9 for .ts docs (satisfies), 5.0 when any doc is JavaScript. Below it, the check is skipped with that reason.
  • A doc the compiler can't parse once its type check is added is listed as not checked, never counted as a pass.
  • A wrong-typed type below the top level (a content block's, a prop's) sets aside the object that holds it.

After an independent audit

A reviewer who didn't write the code drove the real CLI over 53 package shapes on main, on each PR's head, and on all five merged together, and sorted Core's findings by hand. Fixed here, each with a test that failed first:

  • A missing top-level field stayed hidden while nested fields were wrong; it showed only on a later run. A nested object missing a field is now set aside too.
  • A translation that carries props was told to add name, displayName and usage, which only the base doc needs, although the CLI renders it correctly. That was all 41 of Core's docsZh usage missing_field entries.
  • A doc that re-exports its value (export {default} from './shared.mjs') was listed as exporting no doc. It now says the check doesn't follow re-exports.
  • Messages from later passes quoted the edited text ('{ guidance: any; }'). They say "this value" now.

After the next review

After the review that followed

  • With only TypeScript 7 installed, which has no createProgram, the check said TypeScript wasn't installed and asked for it. It names the installed version now and says it can't read types with it. project-typescript.mjs and its doc-loads entry change the same way, byte for byte, in feat(cli): compare documented component props with the TypeScript source #7663.
  • A translation that carries props was told to add each prop's type, although the overlay always takes type from the base doc. Props and sub-component entries in a translation merge onto the base doc by name, so they are checked with every field optional but name; a field they do write must still match its type.

After a release review

  • TemplateDoc is back to main's, byte for byte, with category a TemplateCategory. Widening it would have broken code that reads category as a TemplateCategory, and that can't ship in a patch. An integration's templates are checked against a new IntegrationTemplateDoc instead: every TemplateDoc field, with the package's own category string. It is exported from @astryxdesign/cli/authoring and documented in its own section of astryx docs authoring. Core's own templates keep TemplateDoc. The template reference, the loader's comment and the changeset no longer mention widening.
  • A .doc.ts doc's own TypeScript annotation is set aside the way a JSDoc @type is. Around a valid component doc, export const docs: ReferenceDoc = {…} was told both to remove and to add displayName, props and usage, and <ReferenceDoc>{…} was told to add them. Both now check once, against the root's type.

After the latest review

Rebased

  • Onto main after the change that lists family members under their own names. Nothing in this PR changed, and its tests pass on the new base.

After the review of the doc's own type

  • A doc whose own type was wrong reported only the type: its unknown and missing fields stayed hidden until the stamp was fixed, because the check never set the doc's own type aside. Where the root alone fixes the doc's type (components, hooks, templates, themes, and translations), a wrong stamp is reported and set aside now, and the rest of the doc is checked against the root's type. Where the type picks the type (a docs-root topic), only the stamp is reported, and the doc is listed as not fully checked, so it can't count toward [ok]. What it would report against a type its stamp doesn't name is a guess.
  • The test is a components-root doc with type: 'generic', an unknown field and a missing field, reporting all three, beside a docs-root topic with a misspelled stamp, listed as not fully checked. It failed before.

Merge order

The order is required:

  1. feat(cli): read a package's TypeScript project the way tsc reads it #7681 (reading the package's TypeScript project), then feat(cli): compare documented component props with the TypeScript source #7663 (the prop comparison, stacked on feat(cli): read a package's TypeScript project the way tsc reads it #7681), then feat(cli): doctor integration components compares documented props with the code #7676 (props in doctor integration components, stacked on feat(cli): compare documented component props with the TypeScript source #7663), then feat(cli): read two written TypeScript types as one when they differ only in how they are written #7684 (reading two written types as one, stacked on feat(cli): doctor integration components compares documented props with the code #7676), then feat(cli): doctor integration components judges documented prop types against the code #7667 (prop types, stacked on feat(cli): read two written TypeScript types as one when they differ only in how they are written #7684).
  2. feat(cli): check each authored doc against its published authoring type #7664 (the doc type check), after feat(cli): read a package's TypeScript project the way tsc reads it #7681: once feat(cli): read a package's TypeScript project the way tsc reads it #7681 merges, feat(cli): check each authored doc against its published authoring type #7664 is rebased onto main, which takes out the 65 lines the two share and brings it to 1,960, and reviewed again. Then feat(cli): doctor integration validate checks each doc against its published authoring type #7673 (doc types in validate, stacked on feat(cli): check each authored doc against its published authoring type #7664).
  3. fix(cli): doc links to a component, page, or block resolve #7668 (doc links to components and templates) any time before feat(cli): integration check runs every source check for a package's build, with --strict #7665.
  4. feat(cli): integration check runs every source check for a package's build, with --strict #7665 (integration check) last, after a rebase that adds these PRs' fields and codes to its failures list. Merged earlier, it breaks main.

This PR must ship in the same release as integration check, with #7673.

Overlap with open PRs

Size

2,025 changed lines today; 65 of them are project-typescript.mjs and its doc-loads.test.mjs entry, byte-identical with #7681. This PR merges after #7681: once #7681 lands, it is rebased onto main, which takes those lines out and brings it to 1,960, and reviewed again.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

On main 91cfb53 with this branch: copyright, sync-exports, the published ./api type check, readme:check, the four CLI typechecks (strict, json-api, authoring-contract, template-docs), check:changesets, check:cli-structure, check:repo, eslint on the changed files, prettier on the new files, and the full pnpm vitest run --project node suite: 10,302 tests pass. The 21 failures are in the npm pack, packed real-world package, theme resolution and CSS graph tests, plus one browser accessibility test that is flaky there, which fail the same way on current main on the same machine (they need a package registry it doesn't reach). With all nine PRs merged into main, the four doctor integration subcommands were run in text and JSON on 64 package shapes, against main: templates output is byte-identical everywhere; validate and components only add their new section and JSON field; docs differs only where the doc-links PR fixes a link warning. integration check exits 1 exactly when one of the four would, each of its sections is byte-identical to that subcommand's output, and only integration check's three list tests fail until its planned rebase.

@vercel

vercel Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 19, 2026 11:00pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🔄 In progress (for maintainers only) Oct 19, 2026, 11:14 PM UTC

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

No new or modified components detected.

Bundle Size Summary

No component packages changed.

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

40 of 482 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 754,077
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,188
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
RichTextEditor Markdown parity: side by side probe dark 94,866
RichTextEditor Markdown parity: side by side probe light 89,762
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232
ProgressBar With Target Mark probe dark 7,895
ProgressBar With Target Mark probe light 7,895
Markdown Default neutral dark 7,573
Markdown Default neutral light 7,566

and 20 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check in astryx doctor integration validate, the new data.docTypes field and the TemplateDoc.category widening rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main), so a maintainer needs to approve that requirement or identify another governing spec for these additions. Separately from that decision, please address these:

  • A wrong-typed value hides the same doc's missing and unknown fields, and unknown fields stop after six per doc, so one run does not report everything (for example, keyword and a missing displayName only appear after fixing usage.bestPractices[0].guidance).
  • validate-integration.mjs now walks the roots, reads docs and loads the project's TypeScript outside the integration subject's adapter, which architecture:cli-surface INV21 reserves for the adapter.
  • The docs say data.docTypes is present whenever a manifest is found, but it is absent when the manifest fails to load or there are two manifests.

[Automated review]

findings.push(finding);
}
}
if (removedMore) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only an unknown_field queues another pass, so a doc whose first pass reports a wrong_type is never re-checked: its missing and unknown fields stay hidden until that value is fixed. For example, a component doc with keyword, no displayName and guidance: 'yes' reports only usage.bestPractices[0].guidance. Setting reported wrong-typed values aside as well (for example, casting them to any in the virtual text) and re-checking would let one run report everything, as the PR describes.

};

/** How many times a doc is checked again after an unknown field. */
const MAX_PASSES = 6;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a fixed six passes, a doc with more than six unknown fields reports six and silently drops the rest (a doc with eight reports six). Consider looping until nothing new is found, or listing the doc in unchecked when the limit stops it.

const integration = result.found ? result.integration : undefined;
const packageDir = integration?.__packageDir;
const docTypes = integration
? await checkDocTypes(integrationDocFiles(integration), {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This walks the contribution roots, reads every doc and loads the project's typescript from a module that is not the subject's adapter. architecture:cli-surface INV21 keeps file reads, project-module loading and disk discovery in api/integration/_adapter.mjs or an underscore-prefixed helper only it imports.


/**
* Each doc the package ships, checked against the published authoring type
* for its kind. Present whenever a manifest was found. `status: 'skipped'`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docTypes is absent when a manifest is found but fails to load (invalid_manifest) or is duplicated (multiple_manifests), because result.integration is unset then. Suggest “Present when the manifest loads” here, and the same wording in validateIntegration.doc.mjs, the README and response-types.doc.mjs.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check in astryx doctor integration validate, the new data.docTypes field and the TemplateDoc.category widening rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main), so a maintainer needs to approve that requirement or identify another governing spec for these additions. The earlier findings are addressed, and one fix remains regardless of that decision: with a project TypeScript older than 4.9 and only .ts docs, the version guard in doc-types.mjs does not apply and the check prints [ok] Every doc matches its authoring type without checking anything, so please apply the floor to every doc (or treat syntax errors in the instrumented text as not checked) and add a test.

[Automated review]

.split('.')
.map(Number);
const hasJs = files.some(file => !/\.ts$/.test(file.path));
if (hasJs && major < 5) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a project typescript older than 4.9 and only .ts docs this guard does not apply, and the satisfies operator inserted for .ts docs (L563) is a syntax error there, so every doc comes back with no findings and the CLI prints [ok] Every doc matches its authoring type. Applying the version floor to every doc (or treating syntax diagnostics in the instrumented text as not checked) would keep the report from passing silently.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check in astryx doctor integration validate, the new data.docTypes field and the TemplateDoc.category widening rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main), so a maintainer needs to approve that requirement or identify another governing spec for these additions. The version-floor fix works, and one change remains regardless of that decision: a wrong-typed field named type below a doc's top level (a prop's type, a content block's type) is never set aside, so that doc's other unknown and missing fields stay hidden until it is fixed (for example, a topic with titel, no title and a block with type: 'paragraph' reports only the block). Please keep the never-cast rule for the doc's own type, set aside the object that holds a deeper type, and add a test for that case.

[Automated review]

}
} else if (
finding.problem === 'wrong_type' &&
!/(^|\.)type$/.test(finding.field)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This skips every field named type at any depth, so a wrong nested value (a prop's type, a content block's type) is never set aside and the doc is not checked again; the rest of the doc stays hidden. For example, a topic with titel, no title and a block {type: 'paragraph', text: 'x'} reports only sections[0].content[0].type, and a component doc with keyword and props[0].type: 5 reports only props[0].type. Keeping the rule for the doc's own type (finding.field === 'type') and casting the object that holds a deeper type to any would let one run report everything, as the PR describes.

@josephfarina
josephfarina force-pushed the josephfarina/doctor-doc-types branch from 9c0365b to 3640e4c Compare October 18, 2026 00:09
@josephfarina josephfarina changed the title feat(cli): doctor integration validate checks each doc against its published authoring type feat(cli): check each authored doc against its published authoring type Oct 18, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check (checkDocTypes()) and the wider TemplateDoc.category type rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main). A maintainer needs to approve that requirement or identify another governing spec, and decide whether the published category type should widen. Separately from that decision, please drop the new clause in template.doc.mjs that says astryx doctor integration validate warns about a template without displayName or aspectRatio: nothing runs the check on this branch yet, and validate reports no warnings for such a template, so that sentence belongs in #7673.

[Automated review]

{
type: 'prose',
text: 'When it loads, a template may leave out `displayName` and `aspectRatio` and use its own `category`, as integration templates already published do; a block with no `aspectRatio` previews at 1. Write to the type anyway; it is the contract.',
text: 'When it loads, a template may leave out `displayName` and `aspectRatio`, as integration templates already published do; a block with no `aspectRatio` previews at 1. Write to the type anyway; it is the contract, and `astryx doctor integration validate` warns about a template that leaves them out.',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clause isn't true on this branch yet: nothing calls checkDocTypes() until #7673, and astryx doctor integration validate reports [ok] No integration issues found. with 0 warnings for a block template that has neither displayName nor aspectRatio. Suggest dropping it here and adding it in #7673, so the reference stays accurate in whichever release this lands.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check (checkDocTypes()) and the wider TemplateDoc.category rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main), so a maintainer needs to approve that requirement or identify another governing spec, and decide whether the published category type should widen. Separately from that decision, please fix these two:

  • With TypeScript 7 installed (npm's current latest), resolveProjectTypeScript() finds no createProgram, so the check says TypeScript is not installed and asks for it to be installed; report the installed version and that the check cannot use it, and add a test with a 7.x-style stub.
  • A translation that carries props is still told to add each prop's type, because Partial<> relaxes only top-level fields while the overlay always takes type from the base doc; relax the nested entries too (or narrow the stated rule) and add a test.

[Automated review]

path.join(path.resolve(dir), 'package.json'),
);
const ts = require('typescript');
if (ts && typeof ts.createProgram === 'function') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With TypeScript 7 (npm's current latest, 7.0.2) this check fails: its require('typescript') entry exports only version and versionMajorMinor, so this returns null and checkDocTypes() reports “TypeScript is not installed in this project … Install typescript and run this check again”, although TypeScript is installed. Telling “resolved without a compiler API” apart from “not found” (for example, returning the version without ts) would let the report name the installed version and say why the check cannot use it; a stub typescript that exports only version and versionMajorMinor would cover it in a test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed at c058058. An installed TypeScript without createProgram is named: "TypeScript 7.0.0 is installed in this project, but it has no JavaScript compiler API (createProgram) to read types with, so docs were not checked." foundation/discovery/doc-types.test.mjs › "names an installed TypeScript it cannot use, rather than asking to install one" pins both 7.x shapes (an entry that exports no compiler, one that cannot be loaded). The prop comparison gives the same reason, through the same project-typescript.mjs.

// field it leaves out: each field it writes must match, none is required.
if (translation) {
const full = componentType(shape);
return {typeExpr: `Partial<${full.typeExpr}>`, typeName: full.typeName};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial<> relaxes only top-level fields, so a translation whose props carry translated text and leave type to the base doc gets “Add props[0].type: ComponentPropDoc requires it.” for each prop, although overlayProps always keeps the base doc's type (foundation/doc-compiler/overlays.mjs L151–L153). Making the nested prop and sub-component entries partial too (keeping name), or narrowing the rule in the comment above, would match how the CLI reads these translations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed at c058058: a translation's prop and sub-component entries are checked with every field optional but name, as the overlay merges them by name and keeps the base doc's type. foundation/discovery/doc-types.test.mjs › "checks a translation that carries props for its fields, not for what only the base doc needs" pins a prop without type, a sub-component prop without type, a wrong-typed type (still reported) and an entry without name (reported).

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check (checkDocTypes()) and the wider TemplateDoc.category rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main), so a maintainer needs to approve that requirement or identify another governing spec, and decide whether the published category type should widen. The TypeScript 7 skip reason and the relaxed translation entries now work as described. If the widening stays, please also scope the closing note in the template reference (template.doc.mjs, “category uses the shared TemplateCategory taxonomy”) to Core templates, since the category row now lets an integration template use its own value.

[Automated review]

type: 'TemplateCategory | string',
description:
"Functional gallery category following a 'Group - Variant' convention (e.g. 'Dashboard - Analytics', 'Table - Basic', 'Form - Wizard'). The overview groups by the text before ' - '.",
"Functional category. Core templates use a TemplateCategory value following a 'Group - Variant' convention (e.g. 'Dashboard - Analytics', 'Table - Basic', 'Form - Wizard'); the overview groups by the text before ' - '. An integration template may use its own value, and bare `astryx template` groups templates by it.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row now lets an integration template use its own category, but the closing note in this file still says “category uses the shared TemplateCategory taxonomy”, so the reference reads both ways. If the widening stays, scoping that note to Core templates would keep the two consistent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed at fcb28b1: the closing note says the shared TemplateCategory taxonomy is for Core templates, and that an integration template may use its own value.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check (checkDocTypes()) and the wider TemplateDoc.category rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main), so a maintainer needs to approve that requirement or identify another governing spec, and decide whether the published category type should widen and with which Changeset tag, since code that reads category as a TemplateCategory no longer compiles. The closing note in the template reference now matches the category row. Separately from that decision, please set aside a .doc.ts doc's own TypeScript annotation the way the JSDoc @type already is: around a valid component doc, export const docs: ReferenceDoc = {…} is told both to remove and to add displayName, props and usage, and <ReferenceDoc>{…} is told to add them, so a .doc.ts case beside the existing annotation test would pin the fix.

[Automated review]

Comment on lines +432 to +442
for (const wrapper of wrappers) {
// ` satisfies X` / ` as X` after the value.
if (
!ts.isParenthesizedExpression(wrapper) &&
wrapper.end > wrapper.expression.end
) {
if (!ts.isNonNullExpression(wrapper)) {
blankRanges.push([wrapper.expression.end, wrapper.end]);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sets aside only wrappers that end after the value (satisfies X, as X), and blank handles leading JSDoc. In a .doc.ts, a declaration's own type (export const docs: ReferenceDoc = {…}) and a prefix assertion (<ReferenceDoc>{…}) stay in the compiled text. Around a valid component doc, the first yields displayName, props and usage as unknown_field (“Remove …: ReferenceDoc has no field …”) and then, after the set-aside pass, as missing_field (“Add …: SingleComponentDoc requires it.”); the second yields three “Add …” findings. Blanking the declaration's type and the assertion's type, as the JSDoc @type is, and adding a .doc.ts case beside the annotation test would keep each value checked once against its root's type, as the comment above instrument says.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bd4443b. A variable's own annotation and a prefix type assertion are now blanked like the JSDoc @type, so each value is checked once, against its root's type. Test: 'ignores a .doc.ts doc's own annotation and type assertion, as it does a JSDoc one'. Its export const docs: ReferenceDoc = … and export default <ReferenceDoc>… each report only their keyword.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check (checkDocTypes()) and the new published IntegrationTemplateDoc rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main). A maintainer needs to approve that requirement or identify another governing spec for both additions, and to settle whether integration templates should declare their own typed category, since the page-template guide and integration add template still point them at TemplateDoc. Apart from that decision, nothing needs to change: the .doc.ts annotation fix works and TemplateDoc is unchanged, though note that @astryxdesign/cli/doc and @astryxdesign/cli/template also export the two member interfaces, so the release adds three public type names rather than one.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is waiting on a maintainer decision: the doc type check (checkDocTypes()) and the three new published integration template types (IntegrationTemplateDoc and its page and block members) rest on AST-039 FR13, which is only proposed in #7658 (AST-039 is still a draft on main). A maintainer needs to approve that requirement or identify another governing spec for both additions, and to settle whether integration templates should declare their own typed category, since the integration template guide and integration add template still point them at TemplateDoc. Apart from that decision, nothing needs to change: the Changeset now names all three new types, TemplateDoc is unchanged, and the tests and CI pass.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: the doc type check and the new integration template types now rest on architecture:cli-surface INV34, and the tests and CI pass, but one gap against INV34 remains. A doc whose own top-level type is wrong (for example type: 'generic' in the components root, or a misspelled theme stamp) reports only that type, so its unknown and missing fields stay hidden until the stamp is fixed, because checkDocTypes() never sets the doc's own type aside. Please keep checking the rest of the doc against its root's type where the root alone fixes the type (components, themes), list the doc as not fully checked where the stamp picks the type, and add a test with a wrong stamp beside an unknown and a missing field.

[Automated review]

}
} else if (
finding.problem === 'wrong_type' &&
finding.field !== 'type'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A doc whose own type is wrong is never checked again, so TypeScript keeps reporting that type and hides the doc's unknown and missing fields, while architecture:cli-surface INV34 asks for every one. On this head, a components-root doc with type: 'generic', keyword and no displayName reports only type, and the same doc stamped 'component' reports both. Where the root alone fixes the type (components, themes), the type could be set aside once it is reported; where the stamp picks the type, the doc could be listed as not fully checked. A test with a wrong stamp beside an unknown and a missing field would pin it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a623c61. Where the root alone fixes a doc's type (components, hooks, templates, themes, and the translations), a wrong top-level type is reported and set aside, and the rest of the doc is checked against the root's type. Where the type picks the type (a docs-root topic), only the stamp is reported, and the doc is listed as not fully checked, so it can't count toward [ok]. The test is a components-root doc with type: 'generic', an unknown field and a missing field, now reporting all three; it failed before. Next to it, a docs-root topic with a misspelled stamp is listed as not fully checked.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested, for scope only. The doc type check and the new integration template types match architecture:cli-surface INV34, the earlier wrong-type gap is fixed, and the tests and CI pass, but the diff is now 2,025 authored lines, more than the 2,000 a single pull request should carry. project-typescript.mjs and its doc-loads.test.mjs entry (65 lines) are byte-identical with #7681, so rebasing onto #7681 once it merges, or basing this branch on it, brings this PR to 1,960 lines; moving the three Integration*TemplateDoc types, their authoring doc and the Changeset into an earlier PR would also work. No other change is needed.

[Automated review]

@@ -0,0 +1,61 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file and its doc-loads.test.mjs runner entry are byte-identical with #7681, and they are what puts this PR over 2,000 authored lines (2,025; 1,960 without them). Rebasing onto #7681 once it merges, or basing this branch on it, takes them out of this diff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, no code change. This PR now merges after #7681. Once #7681 lands, I'll rebase this branch onto main, which takes the 65 lines it shares with #7681 (project-typescript.mjs and its doc-loads.test.mjs entry) out of this diff and brings it to 1,960 lines, and I'll request a fresh review then.

A foundation check, `checkDocTypes`, reads each doc file's value (the export
its root reads) and checks it the way TypeScript checks `value satisfies
Type` against the type `@astryxdesign/cli/authoring` publishes for its kind,
with the project's own TypeScript. Each unknown field, missing required
field, or value outside its field's type is a warning naming the doc, line,
field, and a fix. A value outside its type, an unknown field, or a nested
object missing a field is set aside so the same run reports what it hid.
Translations are checked against their translation types; one that carries
props overlays the base doc, so none of its fields is required. Without
TypeScript, or with one older than the docs need, nothing is checked and the
report says why.

The published `TemplateDoc` type admits an integration template's own
`category`, which the loader always accepted and bare `astryx template`
groups by.

`astryx doctor integration validate` runs it in the follow-up PR, which also
adds the repository test over Core's own docs.
… and relax translated entries

With only a TypeScript that has no JavaScript compiler API installed
(TypeScript 7), the check said TypeScript was not installed and asked for
it. It now names the installed version and says it cannot read types with
it. A translation that carries props is no longer told to add each prop's
`type`: props and sub-component entries merge onto the base doc by name,
which keeps each prop's type, so a translated entry needs only its name.
…ateDoc, keep TemplateDoc as it is, and set aside a .doc.ts doc's own annotation

`TemplateDoc` is back to main's, `category` a `TemplateCategory`. A new
`IntegrationTemplateDoc` (every `TemplateDoc` field, with the package's own
`category` string), exported from `@astryxdesign/cli/authoring` with its
own section in `astryx docs authoring`, is what the doc type check holds an
integration's templates to; Core's own templates keep `TemplateDoc`.

A `.doc.ts` doc's own annotation (`const docs: ReferenceDoc = …`) and type
assertion (`<ReferenceDoc>{…}`) are set aside the way a JSDoc `@type` is,
so the value is checked once, against its root's type.
@josephfarina
josephfarina force-pushed the josephfarina/doctor-doc-types branch from a623c61 to 2d52ec0 Compare October 19, 2026 22:54
@josephfarina
josephfarina merged commit 17e56d8 into main Oct 19, 2026
47 checks passed
@github-actions
github-actions Bot deleted the josephfarina/doctor-doc-types branch October 20, 2026 06:22

This branch was successfully deployed

1 active deployment
Preview — 2d52ec0f Deployed Oct 19, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant