adding-child-components
Skill131 starsChanged 8 days agoApache-2.0View on GitHub
Adds a new render-null child component (like ChartTooltip, MetricRange, Trendline, BarAnnotation) that carries props into the Vega spec pipeline. Use when creating a component nested inside an existing mark, e.g. <Line><NewChild /></Line>.
In this repository12 other files it gives its agents.
- AGENTS.mdAGENTS.md
- adding-chart-marksSkill
- adding-propsSkill
- chart-featuresSkill
- comparing-time-periodsSkill
- filing-issuesSkill
- fixing-bugsSkill
- implementing-milestonesSkill
- s2-design-tokensSkill
- summarizing-bugsSkill
- writing-rfcsSkill
- writing-s2-docs-pagesSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
18 rules. About 0 are common, 4 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Use this skill when adding a new child component — a React element that carries props into the Vega spec pipeline but renders nothing …
- Very specific: line 10
Read architecture/child-dispatch.md (the sanitize gate — all 6 sanitize functions — and the three dispatch patterns: direct cast, adapter …
- Very specific: line 16
If this work has a GitHub issue (gh issue view <number> --repo adobe/react-spectrum-charts) or comes from an accepted RFC in rfcs/, read it …
- Distinctive: line 22
If the implementation diverges from the issue, update the issue body.
- Very specific: line 67
Create packages/vega-spec-builder/src/line/<name>/<name>Utils.ts with the functions that produce Vega marks from <Name>SpecOptions.
- Very specific: line 69
Update the parent spec builder (lineSpecBuilder.ts) to call the new utils when the collection is non-empty.
- Very specific: line 71
Update lineTestUtils.ts — add <names>: [] to every existing fixture object.
- Very specific: line 92
Update the parent mark's types (line.types.ts) to add <Name>Element to the child union type and add '<names>' to the Omit list in LineProps.
- Very specific: line 115
Create the barrel index.ts and add to components/index.ts.
- Very specific: line 122
Add <Name>.displayName to each applicable function's allowlist
- Distinctive: line 129
Import the component and types
- Very specific: line 130
Add the collection array: const <names>: <Name>Options[] = [].
- Distinctive: line 131
Add the dispatch case (Pattern A, B, or C based on the component's nature)
- Very specific: line 132
Add <names> to the return object
- Very specific: line 134
Update the parent mark's adapter (lineAdapter.ts) to destructure <names> from childrenToOptions and pass it through.
- Very specific: line 148
Spec builder unit tests: Test the mark utils with produce directly using fixture objects.
- Distinctive: line 176
If the parent mark exists in S2, mirror the changes in:
- Very specific: line 186
Add variations for the child component to the parent mark's dashboard and add a PropCoverage<NameProps> entry to its coverage map.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
react-spectrum-charts: Adding a New Child Component
Use this skill when adding a new child component — a React element that carries props into the Vega spec pipeline but renders nothing itself. Examples: ChartTooltip, MetricRange, Trendline, AxisThumbnail, LinePointAnnotation, BarAnnotation.
Read architecture/child-dispatch.md (the sanitize gate — all 6 sanitize functions — and the three dispatch patterns: direct cast, adapter call, recursive) and architecture/file-creation.md. If the component exists in S2, also read architecture/s2-parity.md.
Step 0: Check for a Linked Issue or Accepted RFC
If this work has a GitHub issue (gh issue view <number> --repo adobe/react-spectrum-charts)
or comes from an accepted RFC in rfcs/, read it and treat it as the requirements: the
design, defaults, cross-cutting concerns, and implementation plan. Re-verify file and
function references against the current code; the issue may have been written before other
changes landed.
If the implementation diverges from the issue, update the issue body. If it diverges from an accepted RFC's design, stop and tell the user. If neither exists, proceed as below.
The PR description says Closes #<number> so merging closes the issue.
Implementation Steps
Step 1: Define types in vega-spec-builder
Create packages/vega-spec-builder/src/types/marks/supplemental/<name>Spec.types.ts:
export interface <Name>Options {
/** JSDoc for every field */
someField?: SomeType;
// If it owns child components (tooltip, popover):
chartTooltips?: ChartTooltipOptions[];
}
type <Name>OptionsWithDefaults = 'someField' | 'chartTooltips';
export interface <Name>SpecOptions
extends PartiallyRequired<<Name>Options, <Name>OptionsWithDefaults> {
index: number;
name: string;
// Parent context (add what the mark utilities need):
lineOptions: LineSpecOptions; // only if parent context is needed
}Export from supplemental/index.ts.
Also update the parent mark's types (lineSpec.types.ts) to add the new collection:
// In LineOptions:
<names>?: <Name>Options[];
// In LineOptionsWithDefaults:
| '<names>'Step 2: Implement the mark logic in vega-spec-builder
Create packages/vega-spec-builder/src/line/<name>/<name>Utils.ts with the functions that produce Vega marks from <Name>SpecOptions.
Update the parent spec builder (lineSpecBuilder.ts) to call the new utils when the collection is non-empty.
Update lineTestUtils.ts — add <names>: [] to every existing fixture object.
Step 3: Define React types
Create packages/react-spectrum-charts/src/types/marks/supplemental/<name>.types.ts:
import { <Name>Options } from '@spectrum-charts/vega-spec-builder';
// Simple case: props == options (most supplemental marks)
export interface <Name>Props extends <Name>Options {}
export type <Name>Element = ReactElement<<Name>Props, JSXElementConstructor<<Name>Props>>;
// Complex case: component has children (ChartTooltip, ChartPopover pattern)
export interface <Name>Props extends Omit<<Name>Options, 'chartTooltips'> {
children?: (datum: Datum, close: () => void) => ReactNode;
}Export from supplemental/index.ts.
Update the parent mark's types (line.types.ts) to add <Name>Element to the child union type and add '<names>' to the Omit list in LineProps.
Step 4: Create the component file
packages/react-spectrum-charts/src/components/<Name>/<Name>.tsx:
/* eslint-disable @typescript-eslint/no-unused-vars */
import { FC } from 'react';
import { <Name>Props } from '../../types';
// Destructure with defaults so Storybook discovers them as controls
const <Name>: FC<<Name>Props> = ({
fieldWithDefault = defaultValue,
optionalField,
}) => {
return null;
};
<Name>.displayName = '<Name>';
export { <Name> };Create the barrel index.ts and add to components/index.ts.
Step 5: Register in the correct sanitize function(s)
In packages/react-spectrum-charts/src/utils/utils.ts:
- Import the component
- Identify the correct sanitize function(s) for the component's valid nesting location(s):
sanitizeRscChartChildren,sanitizeMarkChildren,sanitizeAxisChildren,sanitizeAxisAnnotationChildren,sanitizeTrendlineChildren, orsanitizeBigNumberChildren - Add
<Name>.displayNameto each applicable function's allowlist
A component may need to appear in more than one function if it is valid in multiple nesting contexts.
Step 6: Register in childrenAdapter
In packages/react-spectrum-charts/src/rscToSbAdapter/childrenAdapter.ts:
- Import the component and types
- Add the collection array:
const <names>: <Name>Options[] = []; - Add the dispatch case (Pattern A, B, or C based on the component's nature)
- Add
<names>to the return object
Update the parent mark's adapter (lineAdapter.ts) to destructure <names> from childrenToOptions and pass it through.
Step 7: Write tests
Adapter test (lineAdapter.test.ts):
it('should convert <Name> children to <names> array', () => {
const options = getLineOptions({ children: [createElement(<Name>)] });
expect(options.<names>).toHaveLength(1);
});Snapshot test (chartAdapter.test.ts): Find every toStrictEqual assertion that includes the parent mark type and add <names>: [] to the expected shape.
Spec builder unit tests: Test the mark utils with produce directly using fixture objects.
Integration test: Render a story with the child component and assert the correct Vega marks exist in the DOM.
Pseudo-element test (for SonarQube coverage):
test('<Name> pseudo element', () => { render(<<Name> />); });Step 8: Storybook story
Story goes in stories/components/<Name>/. The component: in the story meta should reference the child component, and {...args} should be spread onto the child — not the parent mark:
export default { title: 'RSC/<ParentMark>/<Name>', component: <Name> };
const Story: StoryFn<typeof <Name>> = (args) => (
<Chart {...chartProps}>
<Axis ... />
<<ParentMark> ...requiredProps>
<<Name> {...args} />
</<ParentMark>>
</Chart>
);Step 9: S2 parity
If the parent mark exists in S2, mirror the changes in:
packages/vega-spec-builder-s2/src/types/marks/supplemental/— same type filepackages/react-spectrum-charts-s2/src/components/<Name>/— same component filepackages/react-spectrum-charts-s2/src/rscToSbAdapter/childrenAdapter.ts— same dispatch casepackages/react-spectrum-charts-s2/src/utils/utils.ts— same sanitize registrations
S2-exclusive components (like BarDirectLabel, LineDirectLabel) only exist in S2 — they have no S1 equivalent.
Step 10: Variation Dashboards (S2)
Add variations for the child component to the parent mark's dashboard and add a PropCoverage<NameProps> entry to its coverage map. See AGENTS.md's "Variation Dashboards".
Then add a ## <Name> section with a playground to the parent mark's packages/docs-s2 page and a <PropsTable component="<Name>" /> under its API section (see writing-s2-docs-pages).
Checklist
vega-spec-builder/src/types/marks/supplemental/<name>Spec.types.ts— createdvega-spec-builder/src/types/marks/supplemental/index.ts— export added- Parent
<mark>Spec.types.ts— collection field added toOptionsandOptionsWithDefaults vega-spec-builder/src/<mark>/<name>/<name>Utils.ts— mark logic implemented- Parent
<mark>TestUtils.ts— new collection added to all fixtures as[] react-spectrum-charts/src/types/marks/supplemental/<name>.types.ts— createdreact-spectrum-charts/src/types/marks/supplemental/index.ts— export added- Parent
<mark>.types.ts— child union updated, Omit list updated components/<Name>/<Name>.tsx— created with displayName and return nullcomponents/<Name>/index.ts— barrel createdcomponents/index.ts— export addedutils/utils.ts— added tosanitizeChildrenAND appropriate scoped setchildrenAdapter.ts— case added, collection in return- Parent
<mark>Adapter.ts— destructures and passes new collection <mark>Adapter.test.ts— createElement test addedchartAdapter.test.ts— snapshots updated with<names>: []- Story and integration test created
- S2 parity applied where parent mark exists in S2
- S2 parent mark dashboard — variations and coverage map updated
- S2 parent mark docs page — section, playground, and PropsTable added
yarn verifypasses
Key Things That Go Wrong
Silent drop with no error — If displayName is missing from sanitizeChildren, the component is filtered before the switch runs. Always verify by writing the adapter test first; a passing test (toHaveLength(1)) confirms the pipeline is wired.
Wrong sanitize function — There is no master list. Each sanitize*Children function is independent. A component registered only in sanitizeMarkChildren but not in sanitizeRscChartChildren will be dropped when used as a top-level chart child. Register in every function matching the component's valid nesting locations.
Snapshot tests in chartAdapter.test.ts — These assert the complete ChartOptions shape. Every new collection added to a mark's options (even as []) must appear in the expected objects or the test fails with a strict equality error.
Missing fixture update — If <mark>TestUtils.ts doesn't include the new collection, all existing spec builder unit tests that use the fixture will fail with shape mismatches.
displayName on the wrong object — Set displayName on the component function itself, not on the import. It must be set before the component is used in the sanitizeChildren set or the switch case.
Copyright header missing — Every new .ts/.tsx source file requires the Apache 2.0 copyright block at the top. ESLint enforces this as a hard error. See architecture/file-creation.md for the exact header text. Story files (.story.tsx) are exempt.
Multi-line JSDoc/comments narrating the change — New functions get at most a one-line JSDoc (description + @param/@returns), matching the length of sibling functions in the same file. Do not add paragraphs explaining why the component was added or what was investigated while building it — that belongs in the PR description, never the code. See AGENTS.md's Code Style section for a worked example.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

