Public component API

September 20, 2026 · View on GitHub

{
  "scope": "global",
  "triggers": {
    "public-api": [
      "INV1",
      "INV2",
      "INV3",
      "INV4",
      "INV5",
      "INV6",
      "INV7",
      "INV8",
      "INV9",
      "INV12"
    ]
  }
}

This record defines the shared public API contract for stable Astryx components.

Purpose

Consumers should be able to predict how an Astryx component is imported, controlled, extended, and composed without learning its internal implementation. New API should express caller-owned intent and preserve established behavior.

System model

The installable package is the public contract. It includes:

  • exported components and hooks;
  • exported props and stable supporting types;
  • published component subpaths;
  • documented defaults; and
  • behavior consumers can observe.

Consumer .doc.mjs files explain that contract. They do not create props or policy that the exported code does not have.

Once this record is current, every current component contract lists architecture:public-component-api in its architecture links. Draft component contracts add that link only after this record becomes current, so candidate relationships do not create authority early.

Component and family contracts own narrower behavior. Theme records own theme targets, public theme properties, tokens, and generated styles. Contribution guidance owns the process used to propose and test APIs.

Boundaries and invariants

  • INV1 — Public exports are reachable. A stable component, hook, prop type, and other promised public types are available from their published package entry point or component subpath.
  • INV2 — Names carry the same meaning. Components, hooks, booleans, callbacks, Actions, directions, refs, and native HTML pass-throughs follow the shared naming grammar. Component-specific vocabulary stays local.
  • INV3 — One prop controls one independent concept. A prop does not silently suppress an unrelated prop. A parent does not duplicate state already owned by a composed child.
  • INV4 — New props express caller-owned intent. New public props follow spec:AST-002/DEC-1. A component keeps a decision internal when it can derive the correct result from state, content, layout, context, or platform behavior.
  • INV5 — Accepted consumer props are preserved. For a DOM-owning component, supported DOM, data, ARIA, style, class, and event inputs reach the element that owns the contract. Component-owned accessibility and behavior cannot be accidentally overwritten.
  • INV6 — Styling inputs combine. xstyle, className, and style compose in their documented order instead of replacing one another.
  • INV7 — Event handlers compose deliberately. Consumer handlers and built-in behavior use the shared cancellation contract. A consumer can cancel built-in behavior only where that public API promises cancellation.
  • INV8 — Refs follow React 19. A public DOM component accepts ref as a prop and connects it to the element promised by its contract.
  • INV9 — Released APIs change deliberately. A released prop, type, export, default, or observable behavior is not removed, renamed, or retyped without an explicit compatibility decision and migration.
  • INV10 — Shared subcontracts are linked, not copied. Input Actions, layer behavior, theming, and family-specific rules stay with their owning records.
  • INV11 — Public theme seams pass API admission. A public semantic CSS custom property is admitted only for caller-owned intent that the target's guaranteed CSS property set cannot express. Its theming record owns the exact purpose, stable default/fallback, scope, documentation, evidence, and compatibility contract; an internal styling gap alone does not justify public API.
  • INV12 — Reachability outranks internal labels. A type, context member, hook return, utility signature, default, or operation reachable from a supported package path is public even when its source comment, filename, or current use calls it internal. Privacy requires removing the export or moving the capability behind a non-exported boundary.
  • INV13 — Public deltas route to their authority owner. Every public API update and every public behavior change follows the result and disposition defined by architecture:knowledge-contracts. Tests, a Changeset, or a generated manifest are evidence; none can settle an absent or contradictory contract.
  • INV14 — Shared breakpoint names keep one meaning. Components that accept a theme width point use sm/md/lg/xl/2xl from the active Theme. A below boundary is exclusive, so equality belongs to the wider side.

This record applies to stable public packages. Lab components are not stable public promises until promotion.

Change coupling

  • Adding or changing an exported prop, supporting type, context member, hook return, operation signature, default, package entry point, ref target, or intentional observable contract triggers public-API review. A top-level export need not change for a reachable supporting contract to change.
  • Each review compares declarations and reachable exports from merge-base to head and from current main to the synthetic merge. The generated surface manifest inventories evidence and maps each row to its canonical component, family, architecture, or system owner; it does not decide acceptance.
  • After the generated surface inventory identifies the exact API and behavior delta and its canonical owner, route it under architecture:knowledge-contracts. This record does not redefine review results or PR disposition.
  • Consumer docs change when consumer usage or a documented promise changes, or when the existing docs would otherwise become false. Fixing an implementation defect does not by itself require consumer-doc changes.
  • A new prop includes the admission argument from spec:AST-002/DEC-1; it does not get accepted only because it solves one callsite.
  • A new public semantic CSS custom property includes the same admission argument and shows why the target's guaranteed CSS property set cannot express the caller-owned need. Its detailed contract and evidence stay in the owning theming/component records.
  • A released breaking change includes the compatibility decision and migration evidence required by the release process.
  • A component exposing a named theme width point uses the fixed vocabulary and exclusive upper-edge behavior from spec:AST-012; it does not maintain a parallel hardcoded breakpoint table.
  • Changes to a family-owned API update the family contract rather than copying its rule into this record or every component.
  • A component that accepts or derives a navigation destination follows family:navigation-destinations. Adding a new sink or destination-bearing component updates that family rather than creating a component-local URL rule.

Owning code

  • Public package manifests and component barrels own reachable exports.
  • Component prop/type definitions and runtime behavior own the actual API.
  • BaseProps defines the shared DOM/styling surface for DOM-owning components.
  • .doc.mjs files own consumer-facing prop descriptions, defaults, and examples.
  • Astryx ESLint rules and API/doc tests check the shared contract.

Deciding specs

  • spec:AST-002/DEC-1 — a new public prop requires caller-owned information the component cannot derive.
  • spec:AST-005/DEC-1 — every Astryx-owned navigation exit follows one destination-safety decision.
  • spec:AST-012/DEC-1 and spec:AST-012/DEC-2 — components reuse the fixed theme width names and inclusive-from/exclusive-below edge meanings.

Transition Action sequencing and pending behavior belong to their component or family contract. This record links that owner once it is current; it does not copy the component matrix.

Verification

InvariantEvidenceFailure signal
INV1, INV8Export, public-subpath, prop, and ref checksA promised component/type cannot be imported or its ref cannot reach the contract element
INV2Naming and logical-direction lint/testsEquivalent concepts use conflicting names or physical direction leaks into public API
INV3, INV4Historical API review benchmark and spec:AST-002 evidenceA prop combines unrelated axes or exposes a derivable implementation choice
INV5, INV6, INV7BaseProps/passthrough lint and representative runtime testsConsumer ARIA/data/style/events are dropped, clobber component semantics, or fail to compose
INV9Published-surface, Changeset, migration, and public-type checksA released API changes without explicit compatibility evidence
INV11Public-API admission review plus owning theming/component testsA public semantic custom property exposes derivable or unsupported implementation detail
INV12, INV13Generated declaration/export/behavior inventory plus canonical-owner mappingA reachable supporting type or behavior is called internal, or mechanical evidence is treated as permission to accept it
INV14AppShell.test.tsxA component hardcodes a divergent point or treats equality as below
Consumer-doc projectiondocPropReferences.test.ts and docPropLiterals.test.tsDocs name a nonexistent prop or omit public literal choices

Known verification gaps:

  • The passthrough lint is warning-only while known violations remain.
  • The current source-to-doc completeness matcher still looks for old prefixed props interfaces, so it does not yet prove full prop coverage.
  • No checked-in generated manifest currently proves the complete reachable public delta across supporting types, context members, hook returns, utility signatures, defaults, CLI schemas, and observable behavior for both the branch and synthetic merge. Reviews must build that inventory explicitly until enforcement exists.

These gaps are named here rather than treating partial checks as complete proof.