agentleFS
adobeadobe/react-spectrum-charts/AGENTS.md

react-spectrum-charts

AGENTS.md131 starsChanged 8 days agoApache-2.0View on GitHub

Declarative React library for composing Spectrum data visualizations. Charts are described as JSX component trees; the library converts them to Vega specs at runtime. The two packages touched for most feature work: - vega-spec-builder — spec logic, types, unit tests - react-spectrum-charts — React components, Storybook stories, integration tests The pipeline and three-type system are covered below. Deeper architecture context lives in architecture/*.md, split by topic so you only read what your task needs: The relevant skill file for your…

  • Installs packages

The file

Show its rules and text

We split this AGENTS.md into the rules it gives an agent

36 rules. About 0 are common, 11 are distinctive, and 25 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈11

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 25

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Every mark follows the same three-type pattern, using Line as the canonical example:

    line 66
  2. Distinctive:

    Always implement in this order:

    line 119
  3. Very specific:

    Add the field to LineOptions (optional, with JSDoc)

    line 122
  4. Very specific:

    Add its key to LineOptionsWithDefaults only if it has a runtime default

    line 123
  5. Very specific:

    Add it to the assembled lineOptions object

    line 127
  6. Very specific:

    Add a fixture (LineSpecOptions) for the new option variant

    line 131
  7. Distinctive:

    Test each affected builder function directly using the new fixture

    line 134
  8. Very specific:

    Only add the field to the Omit list if it needs to be *replaced* at the React boundary (e.g.

    line 137
  9. Distinctive:

    Check through existing storybook file names for relevancy first

    line 143
  10. Distinctive:

    Read the existing or relevant story file for the mark first to understand the established story order and grouping before adding a new one

    line 144
  11. Very specific:

    Add the story using bindWithProps() in a position that fits logically with related stories

    line 145
  12. Very specific:

    Run timezone-normalized: cross-env TZ=UTC is set in all test scripts

    line 158
  13. Very specific:

    Always use yarn for all package management and script execution — never npm install, npm run, or npx.

    line 165
  14. Very specific:

    Every PR that changes anything under packages/ needs a .changeset/*.md file, or the PR's changeset check fails.

    line 171
  15. Very specific:

    No release needed (tests, stories, docs, CI, internal refactor): yarn changeset:empty.

    line 175
  16. Very specific:

    Run yarn changeset:status before finishing.

    line 177
  17. Very specific:

    Never put two files with the same title and overlapping export names — Storybook will throw a duplicate story ID error.

    line 295
  18. Very specific:

    Mark and child components: use PropCoverage<Props>, which is exhaustive — tsc fails until every prop is mapped.

    line 301
  19. Distinctive:

    After any feature implementation, verify all of the following before considering the work done:

    line 348
  20. Very specific:

    If a new field was added to a *Options or *SpecOptions type, find all toStrictEqual snapshot tests that assert the full options shape (e.g.

    line 351
  21. Very specific:

    If a new case X.displayName: was added to childrenAdapter.ts, the corresponding adapter test (e.g.

    line 354
  22. Very specific:

    If a new for...of options.<collection>.entries() loop was added to a spec builder, the corresponding addMarks() test block must have a test …

    line 357
  23. Very specific:

    When asserting properties on Vega encode objects, use toHaveProperty('key', value) rather than obj?.key?.value.

    line 360
  24. Distinctive:

    When adding a new mark, verify its encodings follow the same conventions as comparable existing marks before finalizing.

    line 363
  25. Very specific:

    Any change to an S2 component's props or behavior: must also update that component's docs page and dev dashboard …

    line 380
  26. Very specific:

    Use WebOnly for content that only applies to the interactive page (e.g.

    line 390
  27. Very specific:

    Check the export with a production build (npx docusaurus build --out-dir /tmp/docs-s2-build in packages/docs-s2, then read <route>.md).

    line 391
  28. Very specific:

    Every exported S2 component, including lab, shows its stage (see packages/docs/docs/spectrum2/stages.md) in three places.

    line 398
  29. Distinctive:

    Before writing any code, always:

    line 418
  30. Distinctive:

    If a Jira ticket is associated with the work, check it for explicit spec values (line widths, pixel dimensions, thresholds, token names) …

    line 420
  31. Very specific:

    Check for a linked GitHub issue or an accepted RFC in rfcs/ — if one exists, the matched skill file's Step 0 has you read it and start from …

    line 426
  32. Distinctive:

    Follow the steps in the matched skill file

    line 427
  33. Very specific:

    Keep SKILL.md under 500 lines.

    line 438
  34. Distinctive:

    Use only agent-agnostic syntax: no tool-specific argument placeholders, shell injection, or slash-command references.

    line 439
  35. Very specific:

    New chart types: start in lab (no RFC); see planning/README.md.

    line 469
  36. Very specific:

    When looking up S2 component API and props, read packages/docs/docs/ — particularly docs/api/ and docs/spectrum2/

    line 485

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md 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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

react-spectrum-charts

Declarative React library for composing Spectrum data visualizations. Charts are described as JSX component trees; the library converts them to Vega specs at runtime.


Package Structure

packages/
  react-spectrum-charts/       # Public React component library (@adobe/react-spectrum-charts)
  vega-spec-builder/           # Vega spec generation logic (@spectrum-charts/vega-spec-builder)
  vega-spec-builder-s2/        # Spectrum 2 variant of the spec builder
  react-spectrum-charts-s2/    # Spectrum 2 React component variant
  core-s2/                     # S2-only constants, locales, tokens, utils (subpath exports)
  constants/                   # S1 shared string constants (signal names, data field names, defaults)
  themes/                      # S1 color schemes and runtime token resolution
  utils/                       # S1 shared utility functions
  locales/                     # S1 i18n strings
  dev-tools/                   # Internal contributor CLI tools (not published)
  mcp/                         # MCP server

The two packages touched for most feature work:

  • vega-spec-builder — spec logic, types, unit tests
  • react-spectrum-charts — React components, Storybook stories, integration tests

Architecture Reference

The pipeline and three-type system are covered below. Deeper architecture context lives in architecture/*.md, split by topic so you only read what your task needs:

  • architecture/mark-internals.md — the four spec builder functions, data sources, scale system, interactive mark system
  • architecture/rendering-and-signals.md — VegaChart's two render effects, signal architecture, COMPONENT_NAME, safeClone
  • architecture/child-dispatch.md — the child component dispatch pipeline and sanitize gate
  • architecture/encoding-and-props.md — encoding conventions, callback props, alpha vs stable
  • architecture/s2-parity.md — s1/s2 mirroring rules
  • architecture/file-creation.md — copyright header, import conventions, ScSpec, type literals

The relevant skill file for your change type (see "Before Implementing Any Feature or Bug Fix" below) names which of these apply — you don't need to read all of them.


The Three-Layer Pipeline

React JSX
  ↓  childrenAdapter.ts  — walks the React tree, extracts props into ChartOptions
ChartOptions
  ↓  buildSpec()         — packages/vega-spec-builder/src/chartSpecBuilder.ts
Vega Spec (JSON)
  ↓  vega-embed          — Chart.tsx renders via vega-embed
Rendered chart

Key entry points:

  • buildSpec(chartOptions) — vega-spec-builder/src/chartSpecBuilder.ts — produces a complete Vega spec from a ChartOptions object
  • rscPropsToSpecBuilderOptions() — react-spectrum-charts/src/rscToSbAdapter/childrenAdapter.ts — converts the React component tree to ChartOptions
  • Chart.tsx — react-spectrum-charts/src/Chart.tsx — top-level component, calls useSpec() → buildSpec()

Type System

Every mark follows the same three-type pattern, using Line as the canonical example:

TypeLocationPurpose
LineOptionsvega-spec-builder/src/types/marks/lineSpec.types.tsAll fields optional. Public contract — input to buildSpec.
LineSpecOptionssame fileLineOptions with required defaults applied + internal fields (idKey, index, colorScheme, etc.). Used inside spec builder only.
LinePropsreact-spectrum-charts/src/types/marks/line.types.tsReact component props. Extends LineOptions via Omit — replaces child-type fields with children?: ReactElement, callbacks with onClick? etc.

LineOptionsWithDefaults (in lineSpec.types.ts) is a union of string literals naming every field that has a runtime default. It drives the PartiallyRequired utility type used by LineSpecOptions. Adding a key makes that field required (non-nullable) in LineSpecOptions.

<Mark>Options also carries the markType discriminant and child collection arrays (e.g. chartTooltips?: ChartTooltipOptions[]). <Mark>SpecOptions additionally receives chart-level internal fields that chartSpecBuilder.ts spreads into every mark:

const specOptions = { colorScheme, idKey, highlightedItem, s2 };
// ...
return addLine(acc, { ...mark, ...specOptions, index: lineCount });

addLine — How a Mark Gets Built

addLine in lineSpecBuilder.ts is the produce-wrapped function that mutates the spec for a single line mark. It:

  1. Applies all defaults and assembles LineSpecOptions
  2. Calls addData() — data sources, time transforms, tooltip/popover data, trendlines, metric ranges
  3. Calls addSignals() — hover/select signals, tooltip signals
  4. Calls setScales() — x/y scales, color/lineType/opacity scales
  5. Calls addLineMarks() — the mark group, voronoi hover overlay, static points, trendline marks

Each of those four functions is exported and tested independently.


Key Constants (packages/constants/constants.ts; S2 copy in packages/core-s2/src/constants/constants.ts)

ConstantValuePurpose
TABLE'table'Root Vega data source name
FILTERED_TABLE'filteredTable'Filtered/transformed data source
MARK_ID'rscMarkId'Unique row ID added by identifier transform
SERIES_ID'rscSeriesId'Series identifier field
HOVERED_ITEM'hoveredItem'Signal name suffix for hover state
HOVERED_SERIES'hoveredSeries'Signal name suffix for hovered series
COLOR_SCALE'color'Ordinal color scale name
LINE_TYPE_SCALE'lineType'Ordinal line type scale
OPACITY_SCALE'opacity'Ordinal opacity scale
DEFAULT_METRIC'value'Default y-axis data field
DEFAULT_TIME_DIMENSION'datetime'Default x-axis data field

Adding a Property to an Existing Mark

Always implement in this order:

  1. vega-spec-builder/src/types/marks/lineSpec.types.ts
    • Add the field to LineOptions (optional, with JSDoc)
    • Add its key to LineOptionsWithDefaults only if it has a runtime default
  2. vega-spec-builder/src/line/lineSpecBuilder.ts
    • Destructure the new field (with its default if any) inside addLine's produce callback
    • Add it to the assembled lineOptions object
    • Implement the vega spec change in whichever of addData, addSignals, setScales, addLineMarks is affected
  3. vega-spec-builder/src/line/lineTestUtils.ts
    • Add a fixture (LineSpecOptions) for the new option variant
  4. Unit tests — lineSpecBuilder.test.ts, lineMarkUtils.test.ts, etc.
    • Test each affected builder function directly using the new fixture
  5. react-spectrum-charts/src/types/marks/line.types.ts
    • Only add the field to the Omit list if it needs to be replaced at the React boundary (e.g. swapped for a ReactNode or callback). If it's a plain value prop it passes through LineOptions automatically — do nothing.
  6. react-spectrum-charts/src/components/Line/Line.tsx
    • Thread through a default value if needed (Line.tsx is a render-null component that holds prop defaults)
  7. Storybook story — required for every new feature
    • Check through existing storybook file names for relevancy first
    • Read the existing or relevant story file for the mark first to understand the established story order and grouping before adding a new one
    • Add the story using bindWithProps() in a position that fits logically with related stories
    • defaultArgs already sets dimension, metric, scaleType, color — don't repeat them
    • Export the new story at the bottom of the existing export list

Test Conventions

  • Unit tests live next to the source file they test (lineSpecBuilder.test.ts beside lineSpecBuilder.ts)
  • Internal test fixtures live in <mark>TestUtils.ts (e.g. lineTestUtils.ts) — never import these outside vega-spec-builder
  • Tests call spec builder functions directly with produce — no React rendering
  • initializeSpec() from specUtils.ts creates a minimal starting spec for tests
  • defaultSignals from specTestUtils.ts is the baseline signal array all specs start with
  • Run timezone-normalized: cross-env TZ=UTC is set in all test scripts
  • Default to yarn test:quiet --testPathPattern=<pattern> for a targeted, coverage-free run during development — never yarn workspace ... test (not a valid command in this monorepo) or a bare npx jest. Reserve the full yarn test (collects coverage, feeds Sonar) for CI or an explicit "run all"/final-validation request. A broad run isn't needed to catch unrelated regressions — the pipeline catches those.

Package Management

Always use yarn for all package management and script execution — never npm install, npm run, or npx. This is a yarn workspaces monorepo. To install a package at the workspace root, use yarn add -DW <package> --ignore-engines.


Changesets

Every PR that changes anything under packages/ needs a .changeset/*.md file, or the PR's changeset check fails. Add it in the same PR:

  • Release-worthy change: write .changeset/<slug>.md with frontmatter listing each changed package and its bump (patch, minor, major), then one consumer-facing summary line. Keep change descriptions brief and relevant. The S1 pair (@adobe/react-spectrum-charts, @spectrum-charts/vega-spec-builder) and S2 pair (@spectrum-charts/react-spectrum-charts-s2, @spectrum-charts/vega-spec-builder-s2) release together; list both, as existing changesets do.
  • Describe what consumers will notice (a new prop, a fixed behavior, a smoother animation), not how it was done. Don't name internal functions, signals, or data sources.
  • No release needed (tests, stories, docs, CI, internal refactor): yarn changeset:empty.

Run yarn changeset:status before finishing. See PUBLISHING.md.


Common Commands

# Before running tests in a fresh worktree or after cloning, verify node_modules are installed.
# If `cross-env: command not found` appears, run `yarn install` first, then retry.
yarn install

# Before pushing: the PR checks that catch code problems (plus a local Sonar approximation), minus tests and Storybook/docs builds
yarn verify

# Run tests, targeted (default for agents/dev — no coverage, quiet)
yarn test:quiet --testPathPattern=line

# S2 browser tests (Playwright, packages/react-spectrum-charts-s2/browser/). Starts the dev Storybook, or reuses one
# already on STORYBOOK_PORT (default 6011). First run: yarn playwright install chromium, then record the uncommitted
# screenshot baselines with --update-snapshots
yarn test:browser

# Full suite with coverage — CI/Sonar only, or when the user explicitly asks (e.g. "run all")
yarn test

# Watch mode
yarn watch

# Lint — append --quiet yourself for a quieter run (errors only); leave the
# shared script as-is so CI and other developers still see warnings by default
yarn lint --quiet

# Local SonarCloud approximation on files changed vs. origin/main (included in yarn verify)
yarn lint:sonar            # --all for every package, --base <ref> for another base

# TypeScript check (no emit) — append --pretty false yourself for compact,
# uncolored output; don't bake this into the shared script (humans want the
# colorized code-frame in their own terminal)
yarn tsc --pretty false

# Storybook (port 6009)
yarn storybook
# or
yarn start

# Storybook S2 variant (port 6010)
yarn storybook:s2

# Storybook S2 dev variant (port 6011)
yarn storybook:s2:dev

# Build all packages (respects dependency order)
yarn build

# Build in parallel (faster, use for full rebuild)
yarn build:parallel

# Build s2 packages. These depend on s1 packages being built
yarn build:s2

# Build a single package
yarn workspace @spectrum-charts/vega-spec-builder build

# S2 animation refactors that should not change rendering: record on the base branch, compare on yours
# (yarn build:s2 after each checkout). Exits 1 if any sampled scenegraph frame differs; also reports µs/frame.
yarn perf:parity --out perf-results/parity-before.json
yarn perf:parity --compare perf-results/parity-before.json

Storybook Story Pattern

Every new feature must have a Storybook story. Before adding one, read the existing story file for the mark to understand the current story ordering and grouping — insert the new story in a position that fits logically with related stories rather than appending blindly to the end.

// All stories follow this pattern:
const MyStory: StoryFn<typeof Line> = (args): ReactElement => {
  const chartProps = useChartProps(defaultChartProps);
  return (
    <Chart {...chartProps}>
      <Axis position="left" />
      <Axis position="bottom" labelFormat="time" />
      <Line {...args} />
    </Chart>
  );
};

export const WithMyFeature = bindWithProps(MyStory);
WithMyFeature.args = {
  ...defaultArgs,
  myNewProp: someValue, // only fields that differ from defaultArgs
};

useChartProps handles responsive sizing. bindWithProps wires up Storybook controls.

S2 Storybook Directory Structure

S2 has two Storybooks:

  • Demo (yarn storybook:s2, build yarn build:storybook:s2) loads only packages/react-spectrum-charts-s2/src/stories/**. Demo stories should be polished public examples: realistic data, no debug UI, no UNSAFE_/unstable_ APIs, limited controls, and no duplicate variations.
  • Dev (yarn storybook:s2:dev, build yarn build:storybook:s2:dev) loads both src/stories/** and src/dev/**. Dev-only stories live in packages/react-spectrum-charts-s2/src/dev/<Chart>/<Category>/....

Demo stories in src/stories/<ComponentName>/ use:

  • <ComponentName>Examples.story.tsx — realistic app examples or Figma design equivalents with title: 'React Spectrum Charts 2/<ComponentName>/Examples'.
  • Features/ — one representative story per feature. Title prefix: 'React Spectrum Charts 2/<ComponentName>/Features'.
  • Lab components (exported from src/lab/) use the React Spectrum Charts 2/Lab/<ComponentName>/... title prefix and tags: ['lab'].

The dev Storybook is the demo plus extra per-chart folders (src/dev/<Chart>/<Folder>/, titled React Spectrum Charts 2/<Chart>/<Folder>/...):

  • Playground — one story per chart/component exposing broad controls.
  • Performance — animation, large-data, timing, and stress stories.
  • Dashboards — variation dashboards and other internal review dashboards.
  • Regressions — real bug reproductions only; each story sets parameters.regression = { description, pr } (one-sentence bug description + fixing PR number), which the dev preview renders above the story; add it in the fix PR.
  • Coverage — test-specific and feature-coverage stories (stories used by tests, edge cases, former demo variants, size/stress checks).

Never put two files with the same title and overlapping export names — Storybook will throw a duplicate story ID error.

Variation Dashboards

Each S2 chart type has a variation dashboard at src/dev/<Chart>/Dashboards/<chart>.dashboard.tsx that renders every prop variation side by side. It exports a dashboard with a coverage map from each component's props to the variation ids that exercise them (types in src/dev/dashboardCoverage.ts):

  • Mark and child components use PropCoverage<Props>, which is exhaustive — tsc fails until every prop is mapped.
  • Siblings (Axis, Legend, Chart, etc.) use SiblingCoverage<Props>, which is partial — only map props that interact with the chart type. These are not enforced, so check them by hand.
  • A prop with no variation yet uses { skip: '<reason>' }; dashboardCoverage.test.ts fails on unknown variation ids or empty skip reasons.

Whenever you add or change a prop, child component, or visual behavior on an S2 mark, check that mark's dashboard and every dashboard where the affected sibling appears: add or adjust a variation for the new behavior and point the coverage entry at it. New S2 chart types get a new dashboard modeled on the Donut one.


Code Style

  • No nested template literals. When a template literal would contain an inner ${} expression that is itself a template literal or ternary producing a string, extract the inner expression into a named const first, then reference it in the outer template. This keeps Vega signal strings readable and avoids linter/code-smell warnings.
// Bad
`outer ${condition ? `inner_a` : `inner_b`} rest`

// Good
const inner = condition ? 'inner_a' : 'inner_b';
`outer ${inner} rest`
  • JSDoc is one description line, plus @param/@returns — never a multi-line rationale. Existing functions in this codebase (e.g. getHighlightBackgroundPoint in linePointUtils.ts) already follow this; match that length, don't expand it. The same one-line-max rule applies to inline // comments on call sites. If a function's behavior genuinely needs more than one line to explain, that explanation belongs in the PR description or a planning/ doc, not the docstring — it will rot in place as the code evolves around it.
// Bad — restates the fix instead of documenting the function
/**
 * Gets a background mark for static points to prevent opacity from revealing the line behind
 * the point. This mark stays fully opaque (no opacity rules) so it always covers the line
 * underneath — needed because a hollow static point's own fill is BACKGROUND_COLOR, and
 * dimming that fill's opacity would otherwise let the line bleed through what's supposed to
 * look like a clean punched-out hole.
 * @param lineOptions
 * @returns SymbolMark
 */

// Good
/**
 * Gets a background to static points to prevent opacity from revealing the line behind the point.
 * @param lineOptions
 * @returns SymbolMark
 */
  • Storybook stories get a one-line comment at most, stating only what a reviewer/tester wouldn't otherwise infer from the story's args (e.g. why a particular prop combination is being isolated). Don't narrate the investigation that led to the story.

Test Completeness Checklist

After any feature implementation, verify all of the following before considering the work done:

1. Snapshot / fixture tests

If a new field was added to a *Options or *SpecOptions type, find all toStrictEqual snapshot tests that assert the full options shape (e.g. chartAdapter.test.ts). The new field must appear in every expected object, set to its default value.

2. childrenAdapter / displayName coverage

If a new case X.displayName: was added to childrenAdapter.ts, the corresponding adapter test (e.g. barAdapter.test.ts) must have a test that passes createElement(X) as a child and asserts the resulting collection has length 1. Follow the pattern of existing popover/tooltip/annotation child tests.

3. Spec builder loop coverage

If a new for...of options.<collection>.entries() loop was added to a spec builder, the corresponding addMarks() test block must have a test passing [{}] for that collection and asserting the correct number and names of added marks. Use the with annotations describe block as a model.

4. Vega type assertions

When asserting properties on Vega encode objects, use toHaveProperty('key', value) rather than obj?.key?.value. Direct property access fails TypeScript because Vega uses ProductionRule<T> union types.

5. Encoding consistency

When adding a new mark, verify its encodings follow the same conventions as comparable existing marks before finalizing. Key rules:

  • Background/halo colors must use { signal: BACKGROUND_COLOR }, not a hardcoded getS2ColorValue(...) call — the signal respects the chart's backgroundColor prop
  • Opacity must use getMarkOpacity() rather than a hardcoded value
  • Cross-check against one or two similar existing marks (e.g. barAnnotationUtils.ts, linePointUtils.ts) to catch any other conventions

6. TypeScript and yarn verify

Run yarn verify once before pushing — not after every file or every test written. It runs the PR checks that catch code problems (changeset, lint, lint:sonar, build:s2, tsc, and package exports) in about a minute. It does not run tests — run targeted tests separately with yarn test:quiet --testPathPattern=<pattern>. yarn test passing does not imply the files are type-correct, and tsc is the only check that type-checks stories, dev dashboards, and tests.


S2 Docs Pages

S2 docs live in packages/docs-s2. Follow .agents/skills/writing-s2-docs-pages/SKILL.md when writing or updating a component page; docs/donut.mdx is the reference page. In short: cover every prop, child, and relevant sibling with interactive playgrounds, register the page in sidebars.ts, add chart types to the search cards (src/search/chartExamples.tsx), and set stage in frontmatter.

Any change to an S2 component's props or behavior must also update that component's docs page and dev dashboard (src/dev/<Component>/Dashboards/). See Step 9 of the skill.

Every page's Markdown export must be enough for an agent to implement what the page shows. "Copy for LLM", "View as Markdown", and the skill export all use the .md that plugins/llmsPlugin.js builds from the rendered HTML, so browser-only content is lost unless it has a Markdown equivalent. Wrap browser-only content in ClientOnly (src/components/ClientOnly.tsx) and pass markdown (code, tables, or text that conveys the same information), or markdown={null} with a noMarkdownReason. ESLint bans importing @docusaurus/BrowserOnly directly, and the production build fails on sections that export empty. Use WebOnly for content that only applies to the interactive page (e.g. "drag the handle"). Check the export with a production build (npx docusaurus build --out-dir /tmp/docs-s2-build in packages/docs-s2, then read <route>.md); the dev server's "View as Markdown" shows raw MDX.


S2 Component Stage Markers

Every exported S2 component, including lab, shows its stage (see packages/docs/docs/spectrum2/stages.md) in three places. Add all three for a new component, and update all three when a component changes stage:

  1. TSDoc — a one-line description plus @experimental (lab), @alpha, or @beta on the component declaration, e.g. components/Line/Line.tsx or lab/components/Scatter/Scatter.tsx.
  2. Storybook — tags: ['alpha'] (or 'lab', 'beta', 'rc') in the meta of every story file for the component, in both src/stories/ and src/dev/. This one tag drives the sidebar and canvas-toolbar badges (.storybook-s2/stageBadge.ts) and the autodocs page badge (.storybook-s2/StageDocsPage.tsx); stage labels live in .storybook-s2/stages.ts.
  3. Docs — stage: alpha (or lab, beta, rc, stable) in the frontmatter of the component's packages/docs-s2 page, which <StageBadge /> renders under the H1. Stage labels and badge variants live in packages/docs-s2/src/stages.json, kept in sync with .storybook-s2/stages.ts.

S2 Variant

vega-spec-builder-s2 overrides specific functions from vega-spec-builder (color resolution, background signals) to use Spectrum 2 tokens. react-spectrum-charts-s2 wraps the S2 spec builder. When working on S2 features, build with yarn build:s2 and run Storybook with yarn storybook:s2.

When fixing a bug or refactoring behavior in an s1 package file, always check whether the corresponding s2 file needs the same change. The packages mirror each other structurally but s2 has no Venn support, no s2 prop, and uses s2-specific imports (vega-spec-builder-s2, react-spectrum-charts-s2, and @spectrum-charts/core-s2/* instead of the shared constants/themes/utils/locales packages). A fix in one without the other leaves the packages inconsistent.

The S2 packages (core-s2, schemas, vega-spec-builder-s2, react-spectrum-charts-s2) are ESM-only ("type": "module"), so relative imports must be fully specified: ./foo.js for foo.ts/foo.tsx, ./dir/index.js for a directory, and with { type: 'json' } on JSON imports. TypeScript (NodeNext) and ESLint enforce this; run node scripts/addRelativeImportExtensions.mjs <package dir>... to fix files in bulk (for example after merging main). Run yarn check:packages after yarn build:s2 to validate the published exports and types. S2 packages must not import S1 packages (@spectrum-charts/constants, themes, utils, locales, vega-spec-builder, @adobe/react-spectrum-charts); use @spectrum-charts/core-s2/{constants,tokens,utils,locales}. ESLint enforces this.


Before Implementing Any Feature or Bug Fix

Before writing any code, always:

  1. If a Jira ticket is associated with the work, check it for explicit spec values (line widths, pixel dimensions, thresholds, token names) before fetching Figma images or doing web research. Ticket descriptions for this project routinely contain the exact implementation values needed.
  2. Classify the change type and read the corresponding skill file:
    • Unexpected or broken behavior → .agents/skills/fixing-bugs/SKILL.md
    • New component used directly inside <Chart> → .agents/skills/adding-chart-marks/SKILL.md
    • New component nested inside an existing mark (e.g. <Line><NewChild /></Line>) → .agents/skills/adding-child-components/SKILL.md
    • New prop on an existing component → .agents/skills/adding-props/SKILL.md
  3. Check for a linked GitHub issue or an accepted RFC in rfcs/ — if one exists, the matched skill file's Step 0 has you read it and start from it
  4. Follow the steps in the matched skill file

Agent Skills

Skills live in .agents/skills/<name>/SKILL.md and follow the Agent Skills spec, so any compatible agent discovers them. When adding or editing one:

  • Frontmatter has only spec fields; name and description are required. name is lowercase-hyphenated and matches the folder name; workflow skills use gerund names (fixing-bugs), reference skills use noun names (s2-donut).
  • description is third person, says what the skill does and when to use it, and front-loads the trigger. Agents pick skills from it alone.
  • One skill per topic. Put related detail in that skill's references/ folder rather than splitting it into many small skills (see s2-donut). Skill folders sit directly under .agents/skills/; agents don't discover nested ones.
  • Keep SKILL.md under 500 lines; put long reference material in the skill's references/ folder, linked one level deep.
  • Use only agent-agnostic syntax: no tool-specific argument placeholders, shell injection, or slash-command references.
  • chart-features and s2-design-tokens are generated; edit their source and run yarn skills:update-chart-features / yarn skills:update-s2-design-tokens instead.

Adding a Chart-Level Prop

Chart-level props (on <Chart> itself, not a mark) follow a different file path than mark props:

  1. packages/constants/constants.ts (S1) and/or packages/core-s2/src/constants/constants.ts (S2) — add the type and any associated runtime constants (breakpoints, lookup maps)
  2. vega-spec-builder/src/types/chartSpec.types.ts and vega-spec-builder-s2/src/types/chartSpec.types.ts — add to ChartOptions (import the type from @spectrum-charts/constants in S1, @spectrum-charts/core-s2/constants in S2)
  3. react-spectrum-charts-s2/src/chartUtils.ts — add helper functions and update applyChartPropsDefaults if there's a default
  4. react-spectrum-charts-s2/src/Chart.tsx — compute the effective value (explicit prop vs. auto-derived), override in rscChartProps
  5. react-spectrum-charts-s2/src/RscChart.tsx — destructure and pass to useSpec
  6. react-spectrum-charts-s2/src/hooks/useSpec.tsx — accept the param, pass to rscPropsToSpecBuilderOptions, add to the dep array

SanitizedSpecProps and RscChartProps pick up optional ChartOptions fields automatically via Omit<ChartOptions, ...> — no explicit change needed unless making the field required.

rscPropsToSpecBuilderOptions spreads ...specProps, so any field in ChartOptions flows through to the spec builder without touching the adapter.


Planning: Issues, RFCs, and Research

The full process is in planning/README.md. In short:

  • Bugs and small features are GitHub issues. When the user describes a bug or observation and wants to note it for later, use the filing-issues skill (.agents/skills/filing-issues/SKILL.md). It investigates the root cause and drafts the issue for the user's approval before creating it.
  • New chart types start in lab (no RFC); see planning/README.md.
  • Significant or uncertain changes (new subsystems, cross-chart API or behavior changes) get an RFC in rfcs/<slug>.md, reviewed as a PR. Use the writing-rfcs skill (.agents/skills/writing-rfcs/SKILL.md). An RFC is accepted when two maintainers approve and it merges; its scope becomes issues.
  • Stage planning (what a component's alpha, beta, or RC includes) is done by maintainers as per-component milestones (e.g. Line beta), not RFCs.
  • Implementing a milestone as stacked PRs uses the implementing-milestones skill (.agents/skills/implementing-milestones/SKILL.md).
  • Open-ended investigation goes in planning/research/ (see TEMPLATE.md), and feeds an RFC or issues once it reaches a recommendation.

API Documentation

When looking up S2 component API and props, read packages/docs/docs/ — particularly docs/api/ and docs/spectrum2/

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.