Repository navigation
feat(cli): a component doc can name a differently named export of the file beside it - #7659
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewNo new or modified components detected. Bundle Size SummaryNo component packages changed. Accessibility AuditStatus: No accessibility violations detected. Visual Regression40 of 478 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 20 more. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: no current spec covers finding an integration component by its doc's name when the file is named otherwise, or treating a family doc's components entries as components (spec:AST-039 is still a draft, and its FR4 derives the export from the file stem), so a maintainer needs to provide or identify the governing spec, settling the identity rule proposed in #7658 and whether existing packages get these names without an opt-in when one matches a Core component. Independently of that decision, please split this into two pull requests (export names, then family members) so each stays under 2,000 changed lines, drop the reformatting of untouched code, and fix these two issues:
- A doc whose
namediffers from its file stem, beside a file that exports both, is recorded twice when it lists its own name incomponents, so its singlereplacescounts as two declarations and a replacement that works today stops applying. moduleExportSourcefollowsexport * fromlines inside comments, so a commented-out re-export creates a component thatintegration verifythen rejects withcomponent_export_missing.
[Automated review]
| const name = entry.name; | ||
| if (typeof name !== 'string' || Object.keys(entry).length < 2) continue; | ||
| if (!COMPONENT_NAME_RE.test(name) || hidden.has(name)) continue; | ||
| if (name === stem || name === record.name) continue; |
There was a problem hiding this comment.
This skips the stem and the recorded name but not the doc's own name. When name differs from the stem and the file exports both (Chip.doc.mjs with name: 'AcmeChip' beside a Chip.tsx that exports Chip and AcmeChip), listing AcmeChip as the first full entry, as the family guide suggests, records the same doc twice. Both records then carry the doc's replaces (line 1187), so one valid declaration becomes two, the resolver reports ambiguous_component_replacement, and a replacement that applies on main stops applying. Skipping entries equal to the doc's own name here, and deciding the doc's own record from its identity rather than member lookup, avoids it.
| } | ||
| // A module whose text never writes the export's name as a word can provide | ||
| // it only through `export * from`: follow those without parsing the module. | ||
| if (exportName !== 'default' && !writesWord(text, exportName)) { |
There was a problem hiding this comment.
This shortcut follows every export * from the regex finds, including one inside a comment or string. With // export * from './legacy'; in index.ts and AcmeTab exported by legacy.tsx, it returns legacy.tsx, discovery records AcmeTab as a family member, and integration verify then fails with component_export_missing, where main reports the package packable. Taking the shortcut only when the text contains no export *, or reading the export * targets from the parsed tree as the slow path does, avoids it; the theme import checks share this helper.
2657cea to
6d49bf7
Compare
|
Addressed the automated review in 6d49bf7:
|
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved. No current spec covers finding an integration component by its doc's name when its file is named otherwise, or treating a family doc's full components entries as components: spec:AST-039 is still a draft, and its FR4 derives the export from the file stem. A maintainer needs to provide or identify the governing spec, settling the rule proposed in #7658, including how these names resolve against existing components and whether existing packages get them in a patch without an opt-in. Independently of that decision, please fix these two issues:
- When another installed integration has a file-named component with a documented name, unscoped
astryx component,--props,swizzleandgap-reportexit withERR_AMBIGUOUS_COMPONENTwhere the current release returns that component, sounscopedComponentOwnersshould yield to every file-named owner, not only Core. integration verifyrepeats a failing family doc's errors once per listed entry, and loading each doc once would report it once.
[Automated review]
| export function unscopedComponentOwners(owners, name) { | ||
| const core = owners.filter(owner => owner.package === CORE_PACKAGE); | ||
| const documented = owners.every( | ||
| owner => | ||
| owner.package === CORE_PACKAGE || | ||
| (owner.docPath != null && | ||
| docStem(owner.docPath) !== (owner.name ?? name)), | ||
| ); | ||
| return core.length > 0 && documented ? core : owners; |
There was a problem hiding this comment.
This only yields to Core. If no Core component has the name but another installed integration has a file-named component with it, every owner comes back. Unscoped component, --props, swizzle and gap-report then exit with ERR_AMBIGUOUS_COMPONENT where the current release returns that integration's component. For example, @acme/widgets lists a full AcmeTab entry and @beta/kit ships AcmeTab.doc.mjs.
Keeping only Core and file-named owners whenever at least one exists restores today's owner set for every name that already resolves:
const docRecorded = owner =>
owner.docPath != null && docStem(owner.docPath) !== (owner.name ?? name);
const primary = owners.filter(
owner => owner.package === CORE_PACKAGE || !docRecorded(owner),
);
return primary.length > 0 ? primary : owners;| for (const record of byPath) { | ||
| if (!scans.get(record.name)?.hasMembers) continue; | ||
| for (const member of familyMembers(integration, record)) { | ||
| if (taken.has(member.name)) continue; | ||
| taken.add(member.name); | ||
| members.push({...record, ...member}); |
There was a problem hiding this comment.
Members are recorded from the doc's static read even when the loader rejects the doc. integration verify then loads every record in two places: the inventory in foundation/integrations/contribution-inventory.mjs and validatePackedComponentExports in api/integration/pack-check.mjs. As a result, one broken family doc is reported once per listed entry. With three entries, local_discovery_failed, packed_discovery_failed and component_doc_unloadable each appear three times, where main reports each once. Loading each doc path once in those loops, or skipping the member records of a doc that fails to load, would report it once.
6d49bf7 to
9b990ad
Compare
|
Addressed the second automated review in 9b990ad:
|
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved. No current spec covers finding an integration component by a doc name that differs from its file name, or treating a family doc's full components entries as components: spec:AST-039 is still a draft, and its FR4 derives the export from the file stem. A maintainer needs to provide or identify the governing spec, settling the rule proposed in #7658, how these names yield to existing components (Core's keyword fallback is not kept: astryx component Sheet now returns an integration's Sheet instead of Core's BottomSheet), and whether existing packages get these names in a patch without an opt-in. Independently of that decision, please fix these two issues:
- When a doc's file stem is a Core name and its
nameis another export (Badge.doc.mjsnamingAcmeBadge),component Badgeandswizzle Badgestill exit withERR_AMBIGUOUS_COMPONENT, whiledoctor integration componentsno longer reports the conflict andgap-report Badgeroutes to Core, so that stem needs one rule on every surface. - The derived discovery caches are keyed only on the first file's size and mtime, so a long-lived process keeps a re-exported member after its file stops exporting it.
[Automated review]
| owner => | ||
| owner.package === CORE_PACKAGE || | ||
| owner.docPath == null || | ||
| docStem(owner.docPath) === (owner.name ?? name), |
There was a problem hiding this comment.
This keeps the stem of a doc that is recorded under another export as a file-named owner. When that stem is a Core name (Badge.doc.mjs naming AcmeBadge, beside a Badge.tsx that exports only AcmeBadge), component Badge and swizzle Badge still exit with ERR_AMBIGUOUS_COMPONENT, but doctor integration components no longer reports the conflict, because it reads the discovered name AcmeBadge, and gap-report Badge routes to Core. Treating such a stem as doc-recorded here would let it yield to Core, as the export name already does, and bring detail and swizzle in line with gap-report and Doctor.
| importedModule(integration, record.docPath, doc) ?? record.sourcePath; | ||
| if (module == null) return []; | ||
| return names.flatMap(name => { | ||
| const sourcePath = readOnce(`member\0${module}\0${name}`, [module], () => |
There was a problem hiding this comment.
This cache is keyed only on module's size and mtime, but moduleExportSource follows re-exports into other files. In one process, after slide.tsx stops exporting AcmeSlide while index.ts (export * from './slide') is unchanged, discovery still records AcmeSlide. The name cache above, keyed on the doc and source only, has the same gap. Parsed modules are already cached per file, so dropping these derived caches, or stamping every file the answer read, avoids it.
9b990ad to
1e5c825
Compare
|
Addressed the third automated review in 1e5c825:
|
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: no current spec covers finding an integration component by a doc name that differs from its file name, or treating a family doc's full components entries as components (spec:AST-039 is still a draft, and its FR4 derives the export from the file stem). A maintainer needs to provide or identify the governing spec, settling the rule proposed in #7658, how these names yield to existing components (Core's keyword fallback is not kept: astryx component Sheet now returns an integration's Sheet instead of Core's BottomSheet), and whether existing packages get these names in a patch without an opt-in. The earlier review findings are fixed, and independently of the spec decision, one regression remains to fix:
astryx gap-report Slides, for aSlides.doc.mjsthat namesAcmeCarousel(or a kebab-caseartifact-viewer.doc.mjs), now routes the report to Core, while the current release routes it to the integration andcomponent Slidesandswizzle Slidesstill resolve to the integration, so gap-report should match a renamed doc's file name the way those commands do.
[Automated review]
| ...new Set( | ||
| records | ||
| .filter( | ||
| unscopedComponentOwners( |
There was a problem hiding this comment.
This owner set sees only discovered names, and a doc recorded under another export is now discovered only under that export. So gap-report Slides, for a Slides.doc.mjs that names AcmeCarousel, finds no owner and falls back to Core (line 204). Meanwhile component Slides and swizzle Slides still resolve to the integration, and the current release routes this report to the integration. Matching records whose doc stem equals bareComponent here as well, before unscopedComponentOwners, would keep Core for a Core-named stem (Badge) and route Slides to its package.
1e5c825 to
154b3a1
Compare
db4b024 to
e75b79f
Compare
|
Addressed the latest automated review in e75b79f:
|
e75b79f to
3fb88dc
Compare
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: no current spec lets a component doc's name bind a differently named export of its same-stem file (spec:AST-039 is still a draft whose FR4 derives the export from the file stem, and #7658 has no approval), so a maintainer needs to provide or identify the governing spec, settling that rule, how these names yield to existing answers (for example, astryx component Sheet now returns a package's Sheet instead of Core's BottomSheet), and whether existing packages get this in a patch without an opt-in. The earlier legacy-docs findings are fixed; independently of the spec decision, one small fix remains:
astryx gap-report <Name>still sends a name a doc records to that integration when a hidden file-named doc in another package, or a legacy docs package, owns the name inastryx component, so gap-report should apply the same owner rule ascomponentandsearch(main sends both reports to Core).
[Automated review]
| ) | ||
| .map(r => ({...r, hold: 'core'})), | ||
| ...project.loadedIntegrations.flatMap(integration => | ||
| discoverIntegrationComponents(integration).flatMap(r => |
There was a problem hiding this comment.
This owner set misses two owners that component and search count. discoverIntegrationComponents skips hidden docs, so another package's hidden AcmeTab.doc.mjs never makes @acme/widgets' recorded AcmeTab yield, and no legacy check runs, so a legacy docs package documenting AcmeCarousel does not either. Both reports now go to @acme/widgets, while component AcmeTab answers with the hidden doc's package, component AcmeCarousel shows the legacy doc, and main sends both reports to Core. Counting every same-stem doc as a file owner here (through findIntegrationComponent, as detail and swizzle do) and dropping doc owners of an export name a legacy docs package documents would restore main's routing.
3fb88dc to
ba786cc
Compare
|
Addressed the latest automated review in ba786cc:
|
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: no current spec lets a component doc's name bind a differently named export of its same-stem file (spec:AST-039 is still a draft whose FR4 derives the export from the file stem, and #7658 has no approval), so a maintainer needs to provide or identify the governing spec, settling that rule, how these names yield to existing answers (for example, astryx component Sheet now returns a package's Sheet instead of Core's BottomSheet), and whether existing packages get this in a patch without an opt-in. The earlier gap-report findings are fixed for the exact spelling; independently of the spec decision, one small fix remains:
astryx gap-reportmatches names without case, but its new hidden-doc and legacy-package checks use the name as typed, sogap-report acmetaborgap-report acmecarouselstill sends the report to the integration that only records the name, wheregap-report AcmeTaband main send it to Core.
[Automated review]
| const found = findIntegrationComponent(integration, bareComponent); | ||
| return found?.hold === 'file' | ||
| ? [{...found, package: integration.name}] | ||
| : []; | ||
| }), | ||
| ]); | ||
| // A legacy docs package keeps an export name a doc records, as in detail. | ||
| const owners = [ | ||
| ...new Set( | ||
| records | ||
| (legacyExternalKeeps(project.cwd, bareComponent, ruled) ? [] : ruled) |
There was a problem hiding this comment.
selectTarget matches owners without case (lines 197–205), but this lookup and the legacy check on line 221 run on the typed bareComponent: findIntegrationComponent checks the exact same-stem path, and legacyExternalDocuments compares legacy file names exactly. So gap-report acmetab (another package's hidden AcmeTab.doc.mjs, on a case-sensitive file system) and gap-report acmecarousel (a legacy package documenting AcmeCarousel) still route to @acme/widgets, while gap-report AcmeTab, gap-report AcmeCarousel and main route to Core. Running both checks on the matched records' names, or comparing without case, would make the spellings agree; a lower-case variant in the two gap-report assertions would cover it.
ba786cc to
4e70a55
Compare
|
Addressed in 4e70a55: gap-report's hidden-doc and legacy-package checks now run on every spelling its case-blind match finds, as well as the one typed. |
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: no current spec lets a component doc's name bind a differently named export of its same-stem file (spec:AST-039 is still a draft whose FR4 derives the export from the file stem, and #7658 has no approval), so a maintainer needs to provide or identify the governing spec, settling that rule, how these names yield to existing answers (for example, astryx component Sheet now returns a package's Sheet instead of Core's BottomSheet), and whether existing packages get this in a patch without an opt-in. The case-variant gap-report fix works; independently of the spec decision, one small fix remains:
- When a doc records a Core name that
astryx componentresolves only through Core's sub-component lookup (such asCheckboxIndicatororBaseTable),astryx component CheckboxIndicatorreturns Core's, butastryx searchoffers the package's result with that same bare command andastryx gap-report CheckboxIndicatorfiles with the package, so search and gap-report should count Core the waycomponentdoes (findComponentReadme).
[Automated review]
| // The owner rule, as component detail applies it: a candidate `component | ||
| // <name>` does not open names its package and follows the owner, and a | ||
| // file-name alias that opens another owner is dropped. | ||
| const coreNames = new Set(core.map(candidate => candidate.name)); |
There was a problem hiding this comment.
coreNames holds only the names Core's component list includes (discoverComponents), but component detail also counts Core as an owner wherever findComponentReadme resolves, which covers more names, such as CheckboxIndicator and BaseTable. For a doc that records CheckboxIndicator, opens returns true, so this result keeps the bare astryx component CheckboxIndicator, which opens Core's component instead. Checking findComponentReadme(coreDir, name) here as well would give that hit --package.
| const ruled = unscopedComponentOwners([ | ||
| ...records | ||
| .filter( | ||
| r => r.package === CORE_PACKAGE && r.name.toLowerCase() === wanted, |
There was a problem hiding this comment.
Core counts as an owner here only for names in project.components(), which come from discoverComponents. Component detail and swizzle also count Core wherever findComponentReadme resolves, so gap-report CheckboxIndicator, for a doc that records CheckboxIndicator, now files with the package, while component CheckboxIndicator answers with Core and main sends the report to Core. Adding a core owner when findComponentReadme(coreDir, spelling) resolves would keep the surfaces together.
4e70a55 to
61969eb
Compare
|
Addressed in 61969eb: search and gap-report now count Core for every name Core's doc lookup finds ( |
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: no current spec lets a component doc's name bind a differently named export of its same-stem file (spec:AST-039 is still a draft whose FR4 derives the export from the file stem, and #7658 has no approval), so a maintainer needs to provide or identify the governing spec, settling that rule, how these names yield to existing answers (for example, astryx component Sheet now returns a package's Sheet instead of Core's BottomSheet), and whether existing packages get this in a patch without an opt-in. The sub-component fix for doc-recorded names works; independently of the spec decision, one small fix remains:
- For an integration doc named after its file,
astryx gap-report CheckboxIndicator(orBaseTable, or another Core name onlyfindComponentReadmefinds) now exits withERR_AMBIGUOUS_COMPONENTwhere main sends the report to that package, so the Core owners gap-report adds fromfindComponentReadmeshould only make doc-recorded names yield and should not route reports themselves.
[Automated review]
| ...[...spellings].flatMap(spelling => { | ||
| const coreDir = findCoreDir(project.cwd); | ||
| const docPath = coreDir && findComponentReadme(coreDir, spelling); | ||
| return docPath ? [{package: CORE_PACKAGE, docPath, hold: 'core'}] : []; |
There was a problem hiding this comment.
These Core owners also count against file-named owners. For a CheckboxIndicator.doc.mjs beside a CheckboxIndicator.tsx that exports CheckboxIndicator, astryx gap-report CheckboxIndicator now exits ERR_AMBIGUOUS_COMPONENT, while main routes the report to the integration, because main's owners come only from project.components(), which omits the Core names only findComponentReadme resolves (about 52, such as BaseTable). Marking these owners so they still make a doc owner yield, then dropping them in the final filter on lines 246–248 the way the hidden file-named docs drop out, keeps main's answer for file-named docs and Core's for doc-recorded ones; a file-named CheckboxIndicator case in integration-export-name.test.mjs would pin it.
… file beside it A doc beside a source that exports no runtime name after its stem may set `name` to another runtime export of that source (Slides.doc.mjs naming AcmeCarousel, acme-widget.doc.mjs naming AcmeWidget). Discovery records the component under that export unless the name is taken: every doc's file name, hidden docs included; a Core name a doc in the package replaces; a name an earlier doc records (paths ordered by code unit, the same in every locale). One owner rule decides every exact unscoped lookup: an owner that holds a name only because a doc records it (the export name, or the file name of a doc recorded under another export) yields to Core and to every component named after its file, valid or not. Component detail (--props, --source, batch), swizzle (Core holds every name it documents), gap-report (which also answers to a renamed doc's file name), and search (commands and aliases) apply it after an active replacement and before any check of which docs load. --package still reaches the doc-recorded owner. integration verify reads exports with the shared module-exports reader, so a component re-exported through a folder's index file passes. A doc that fails to load is reported under its file name. Module and doc caches key on size, mtime, ctime and inode, so a file npm rewrites with the same mtime is read again.
61969eb to
7db5b0f
Compare
|
Addressed in 7db5b0f. In gap-report, a Core name that only Core's doc lookup finds now only makes a doc-recorded name yield; it no longer routes the report itself. A doc named after its file ( |
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: no current spec lets a component doc's name bind a differently named export of its same-stem file (spec:AST-039 is still a draft whose FR4 derives the export from the file stem, and #7658 has no approval), so a maintainer needs to provide or identify the governing spec. That decision should settle the identity rule, how these names yield to existing answers (for example, astryx component Sheet now returns a package's Sheet instead of Core's BottomSheet, and astryx component Badge returns Core's Badge where it was ambiguous), and whether existing packages get this in a patch without an opt-in. The earlier gap-report regression is fixed, and nothing else in the code needs to change while that decision is pending.
[Automated review]
There was a problem hiding this comment.
This looks ready to approve. The rule it implements — an integration component doc's name is the runtime export it documents, and a name recorded only that way never takes a lookup that Core, a legacy docs package or a file-named component answers — is now current in architecture:cli-surface INV24, with the precedence details spelled out in spec:AST-039 FR4, and the answers that change for existing packages (astryx component Sheet, astryx component Badge, and integration add component refusing a recorded name) follow that rule. Listing, lookup, search, swizzle, gap-report, Doctor and integration verify behave as described, the targeted CLI tests and CI pass, and the branch merges cleanly with the integration verify change now on main, so no changes are needed.
[Automated review]
…ion-guides-cleanup ComponentDoc.name takes #7659's sentence about the export apps import in place of the file-name rule. The versioning list keeps this branch's published-version bullets and adds #7674's family-member bullet. hiddenComponents now says what #7674's discovery does in an integration: it hides full entries in the doc's own `components`, and a sub-component with its own doc still uses `hidden`.
User need
An integration package documents a component the CLI cannot find by name when the doc's file is not named after the component.
Slides.doc.mjsbeside aSlides.tsxthat exportsAcmeCarousellists asSlides,component AcmeCarouselfinds nothing, search never showsAcmeCarousel, andintegration verifyfails withcomponent_export_missingbecause the module does not exportSlides. Setting the doc'snameto the real export changes nothing.acme-widget.doc.mjsbeside anacme-widget.tsxthat exportsAcmeWidgetlists asacme-widget,component AcmeWidgetfails withERR_UNKNOWN_COMPONENT, andintegration verifyfails once per component. Today the only way out is to rename the package's source files.Problem and solution fit
A component doc's
nameis the export it documents (ComponentDoc.namealready says so). After this change, discovery reads it that way: when the same-stem source has no runtime export named after its stem, the doc'snamemay name another runtime export of that source, and discovery records the component under that name. A source that exports its stem keeps the stem, and the file name still opens the doc.The list,
component <Name>, search,swizzle,gap-report, thedoctor integrationchecks andintegration verifyall know the component by that name.One owner rule keeps every lookup that resolves today resolving the same way, on every surface. Each owner of a name holds it as
core(Core documents it),file(a doc named after its file), ordoc(only a doc records it: the export name, or the file name of a doc recorded under another export). On an exact unscoped lookup,docowners give way whenever acoreorfileowner exists;--packagestill reaches them. Every surface applies it in the same order: an active replacement first, then the rule over every owner (whether its doc loads or not), then the surface's own check of which docs load.Current specification
spec:AST-039FR4 owns how discovery pairs a descriptor with its source. #7658, merged as c992783 on the maintainer's approval, states this rule there and in the current recordarchitecture:cli-surfaceINV24.Public delta and caller burden
astryx component AcmeWidgetexits 1 withERR_UNKNOWN_COMPONENT, andastryx integration verifyreportscomponent_export_missingforacme-widget. After:component AcmeWidgetprints the doc withimport: @acme/widgets,component acme-widgetstill opens it, and verify passes.astryx.docsinpackage.json), or another package's file already has stays theirs. WithOverlays.doc.mjsnamingDialog,component Dialog,--props,--source,swizzleandgap-reportstill answer with Core'sDialog, search shows the package's entry withastryx component Dialog --package @acme/widgets, and Doctor reports the shared name. WithBadge.doc.mjsnamingAcmeBadge,component Badgereturns Core'sBadgeon every surface (today it is ambiguous).replacesthat Core name (the replacement keeps answering it, bare and with--package), or when an earlier doc records it. Docs are ordered by path in code-unit order, the same in every locale.Sheet,component Sheetreturns a package's documentedSheetinstead of guessing Core'sBottomSheet.component BottomSheetis unchanged.nameas the export, which the authoring type already asks for. Nothing else changes for authors or apps. JSON shapes are unchanged; Core component discovery is untouched.[feat].Design and alternatives
Discovery reads docs and sources without running them. A quick text scan picks the docs whose top-level
namediffers from their file name (or may be set by a spread); only those are parsed.foundation/discovery/component-discovery.mjs:discoverIntegrationComponentsrecords a doc that names another export of its file under that export, unless the name is taken.findIntegrationComponent(integration, name)returns the doc, source and hold exactly as discovery records the name: the same-stem doc first, hidden or not, then a doc whose export is the name.findIntegrationComponentDocandfindIntegrationComponentSourceanswer through it.unscopedComponentOwners(owners)applies the owner rule.foundation/discovery/module-exports.mjs:staticExportObjectreads an exported object literal as plain data;moduleExportSourcenames the file that declares an export, following local re-exports;moduleExportsNameanswers through it. A module whose text writes neither the name norexport *is not parsed for it; otherwise the parsed tree decides, so a re-export inside a comment counts for nothing. Parsed modules and docs are reused while their file keeps its size, mtime, ctime and inode.api/component,api/swizzle,api/gap-report: build owners fromfindIntegrationComponentor discovery records and apply the rule in the order above. Swizzle counts Core as owning every name it documents, then leaves a Core name with no folder of its own unknown, as before. gap-report answers to a renamed doc's file name, and its owner rule counts another package's hidden file-named doc and a legacy docs package the way detail does, for every spelling its case-blind match finds (both still route the report to Core, as on main).api/search/search.mjs: applies the rule from the records it already gathered, counting Core for every name its doc lookup finds, as detail does. A candidate thatcomponent <name>would not open gets a command with--packageand ranks after the owner; a file-name alias that opens another owner is dropped.api/component/_adapter.mjs(legacyExternalKeeps),component.mjsandapi/search/search.mjs: when every owner holds the name only as an export a doc records, a legacy docs package's exact doc keeps the name, as on main, and search names the integration's result with--package. A renamed doc's file name still answers before a legacy package, as it always did.ComponentDoc.name(type and self-doc) and the "Resolve the import" guide.Rejected:
spec:AST-039FR11 forbids);ERR_AMBIGUOUS_COMPONENT).Evidence
artifact-viewer.tsx, one in a subfolder):artifact-viewer,artifact-toolbarandviewer-header;component ArtifactViewerand the other two fail withERR_UNKNOWN_COMPONENT;integration verifyfails three times withcomponent_export_missing;ArtifactViewer,ArtifactToolbarandViewerHeader; each opens;component artifact-viewerstill opens its doc;search "artifact viewer"ranksArtifactViewerfirst;integration verifyis packable with no issues.replaces, a Core name, a Core keyword-fallback name, a name another package's file has, two packages recording one name, re-export chains, folder indexes, a re-export in a comment,.doc.ts, legacyexport const docs, and a doc the loader rejects. Every surface (component and its flags, batch, swizzle, gap-report, search, the list,doctor integration validateandcomponents,integration verify,doctor) ran on main and on this branch, plus the JS API twice in one process with files edited between. Result: no exact lookup that answers on main answers differently, every surface agrees on the owner of each name, and nothing crashes or repeats an issue.component Button,component --list,doctor, validate and search are 11% to 20% slower than main, almost all of it the first parse of the docs that name another export or list members.Overlaps
integration verifyread exports with the reader discovery uses, so verify and discovery agree on every export a doc names. This PR merges cleanly onto it.ComponentDoc.namedescription. Whichever lands second takes this wording: "For an integration component it is the export apps import: the doc sits beside the file that exports it, and when that file exports nothing under its own name,namemay name the export it does have."api/component/_adapter.mjs; a merge keeps both imports.Scope
Testing
component-discovery.export-names.test.mjs: the owner rule; hidden-doc and same-package replacement names taken; code-unit path order; a name a spread may set; an unreadable doc reported under its file name; a kebab-case doc found by its export and by its file name.integration-export-name.test.mjs: another package's invalid or hidden file-named doc keeps the name on every surface, gap-report included; a legacy docs package keeps an export name it documents while a renamed doc's file name answers first, and search names the integration's result with--package; a Core name only the sub-component lookup finds (CheckboxIndicator) stays Core's in search and gap-report too, while a doc named after its file (BaseTable) still routes reports to its package, as on main; gap-report by a renamed doc's file name; the search command and order for a Core name; a Core name with no folder stays unknown to swizzle; another package's invalid file-named doc keeps the name on every surface; a file name Core has stays Core's in search; a replacement recorded under its export swizzles into its file-name folder.module-exports.test.mjs: the static readers, and a file npm replaced with one of the same size and mtime is read again.pack-check.test.mjs: verify checks the export a doc names.@astryxdesign/theme-neutralis built; unbuilt, it reports only that package's twobuilterrors.