facebook / facebook/astryx

Integration packages should be able to declare a component as a replacement for a core component

Open
#4,107 1 comment 0 reactions 1 assignee Claimed by @josephfarina View on GitHub
discussion enhancement needs-scoping
Dominant language
TypeScript
Stars
13k
Forks
1.1k
Avg merge
1d 15h
Merged PRs (30d)
690

Description

### Problem

When an integration package ships a component meant to *replace* a core component (e.g. `@acme/ds` provides its own `Dialog` that should be used instead of core's, everywhere), there's no way to express that relationship. The CLI treats both as equal providers:

- `component --list` shows both Dialogs
- `component Dialog` throws `provided by multiple packages. Re-run with --package` (correct as a default, but there's no way to resolve it permanently)
- `search dialog` ranks core's Dialog first

For humans this is a papercut. For coding agents it's worse: `AGENTS.md` tells them to discover components through the CLI, and the CLI steers them to the component that's been replaced.

Surfaced by #3910, which proposed a consumer-side `hiddenComponents` denylist in `astryx.config`. Filing the underlying need separately so we can settle the shape before building.

### Why a consumer-side denylist isn't quite the right shape

#3910's approach lives in the consumer's `astryx.config.mjs` as a list of component names to hide. Two concerns:

- **It encodes mechanism, not intent.** "Hide core's Dialog" is not the same as "my Dialog replaces core's." The replacement relationship is lost, so an agent learns "there's one Dialog" but not which package supersedes which, or why.
- **Every consumer repeats it.** The replacement gets re-declared as a string entry in every downstream project's config, and drifts as the integration adds components.

### A direction worth considering

The party that knows about the replacement is the integration package. It already declares what it contributes in its `astryx.integration.mjs` manifest. A replacement could be declared there, by the component that supersedes core, so that:

- discovery resolves to one authoritative provider with no consumer config
- the signal is inherited by anyone who installs the integration
- agents get a real steer ("Dialog is provided by `@acme/ds`, replaces core") instead of a component silently vanishing
- `--package` scoping still reaches the replaced component (docs and swizzle stay reachable)

### Open questions to scope

- Manifest-declared replacement vs. consumer opt-in vs. both? A consumer may want to install an integration without accepting its replacements.
- What does a replacement do to `search` ranking and fuzzy suggestions?
- How is a replacement surfaced in `component --list` and in `AGENTS.md` (shown-as-replaced, hidden, annotated)?
- Conflict handling: two integrations both claim to replace the same core component.
- Does this generalize beyond core (an integration replacing another integration's component)?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.