Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
freem
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Openai/695d28c7-94c8-8006-836b-96dbe559b5eb
(section)
Add languages
Page
Discussion
English
Read
Edit
Edit source
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
Edit source
View history
General
What links here
Related changes
Special pages
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
=== Assistant: Below is a “minimal but enforceable” contract doc you can drop into the repo e.g., packages/ui/COMPATIBILITY.md, follow… === Below is a “minimal but enforceable” contract doc you can drop into the repo (e.g., packages/ui/COMPATIBILITY.md), followed by concrete CI gates (GitHub Actions + a couple config snippets) that actually enforce it. ==== UI Library Compatibility Policy (Contract) ==== <syntaxhighlight lang="md"># UI Library Compatibility Policy (Contract) Applies to: @org/ui (and any sibling packages like @org/ui-tokens) Goal: Component teams can ship improvements without forcing full-product regression testing, while consumer teams can upgrade on their schedule with predictable risk. ==== 1) What is Public API vs Private ==== ===== Public API ===== Only these are considered supported and stable: * Package entrypoints exported from: * <code>@org/ui</code> (root) '' <code>@org/ui/<subpath></code> ''only if explicitly listed in <code>package.json#exports</code>* * Types re-exported from public entrypoints. * CSS variables / design tokens explicitly documented in @org/ui-tokens. * Storybook stories are the canonical behavioral + visual examples for public components. ===== Private API (NO consumer dependencies) ===== Consumers must NOT import: * deep paths like <code>@org/ui/src/...</code> or <code>@org/ui/components/...</code> * any file not reachable via <code>package.json#exports</code> * internal DOM structure (e.g., <code>.kebab > div:nth-child(2)</code> selectors) * internal class names (unless explicitly documented as stable) * test ids unless explicitly documented as stable (prefer role/label-based selectors) Breaking this rule means the consumer owns the breakage risk. ==== 2) Semantic Versioning Rules ==== We use SemVer for @org/ui: ===== Patch (x.y.Z) ===== * bug fixes that do not change intended behavior * performance improvements * internal refactors * docs/story fixes * a11y improvements that do not change visual layout materially ===== Minor (x.Y.z) ===== * new components * new optional props with safe defaults * new variants/slots that do not change existing default rendering * additive token additions ===== Major (X.y.z) ===== Any change that can reasonably break consumers or change user-visible behavior by default: * removing or renaming exports, components, props, events * changing default prop values in a user-visible way * changing DOM in a way that breaks common integration (e.g., removes an element frequently targeted by consumer CSS) unless explicitly declared private * significant visual change outside defined tolerances * token changes that alter appearance beyond tolerances ==== 3) Definition of “Breaking” (Practical) ==== A change is breaking if it causes any of these: * TypeScript compile errors for consumers * runtime errors for consumers * layout/visual regressions outside approved tolerances * behavioral regressions (click, focus, keyboard nav, validation) * a11y regressions (color contrast, missing labels, keyboard trap, etc.) If in doubt, treat as breaking (major) or provide a compatibility path. ==== 4) Compatibility Paths (Preferred over Majors) ==== Before a major, prefer: * keep old prop, introduce new prop; deprecate old prop * support both old and new values for a release window * adapter/wrapper exports (temporary) to keep old behavior * codemod + migration guide ==== 5) Deprecation Policy ==== * Deprecated APIs remain supported for at least: * 2 minor releases OR 60 days (whichever is longer) within the same major. * Deprecated APIs must include: * JSDoc <code>@deprecated</code> with the replacement * a migration note in CHANGELOG * We support at most the last 2 majors at any time. * Older majors receive only critical security/a11y fixes, then are removed on schedule. ==== 6) Consumer Upgrade Policy ==== * Consumers pin versions (no auto-floating to new majors). * Upgrades happen via explicit dependency bump PRs. * Any component library major upgrade requires consumer-side validation. * Patch/minor upgrades should be safe by default; validation can be localized. ==== 7) Required Evidence for Changes ==== Every change to public components must include at least one: * updated Storybook story (new/changed behavior) * unit/integration test (Testing Library / Playwright component test) * visual regression approval (if visuals changed) ==== 8) Ownership ==== Each component has an owner group responsible for: * reviewing breaking changes * maintaining deprecations/migrations * ensuring test coverage is adequate </syntaxhighlight> ==== Enforced boundaries (the part that makes this real) ==== ===== 1) Hard export boundary via package.json#exports ===== In packages/ui/package.json: <syntaxhighlight lang="json">{ "name": "@org/ui", "version": "1.12.0", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "./button": { "types": "./dist/button.d.ts", "default": "./dist/button.js" } } } </syntaxhighlight> This is the key: if it’s not in exports, it’s not importable (or at least you can make it fail in modern Node/bundlers). ===== 2) Ban deep imports with ESLint ===== In the consumer apps’ ESLint config: <syntaxhighlight lang="js">module.exports = { rules: { "no-restricted-imports": [ "error", { "patterns": [ { "group": ["@org/ui/''/''", "@org/ui/src/''", "@org/ui/dist/''"], "message": "Deep imports from @org/ui are forbidden. Import only from public entrypoints defined in @org/ui package exports." } ] } ] } }; </syntaxhighlight> (Adjust patterns to your repo conventions.) ===== 3) Require a Changeset for any UI public change ===== If you use Changesets, you can enforce “no changes to packages/ui without a changeset” in CI. ==== CI gates: what to run on every PR ==== ===== The checks you want ===== # Lint + typecheck (affected scope if you’re using Nx/Turborepo; otherwise run for ui + touched apps) # Unit/integration tests for @org/ui # Build <code>@org/ui</code> (so the export boundary is real) # Public API diff check (type-level breaking changes) # Storybook build + visual regression (Chromatic/Percy) # Changeset presence check for UI changes ===== GitHub Actions PR workflow (example) ===== <syntaxhighlight lang="yaml">name: PR Checks on: pull_request: jobs: ui_contract: runs-on: ubuntu-latest steps: * uses: actions/checkout@v4 * uses: pnpm/action-setup@v4 with: version: 9 * uses: actions/setup-node@v4 with: node-version: 20 cache: "pnpm" * run: pnpm install --frozen-lockfile # 1) Enforce no deep imports via lint * name: Lint run: pnpm -r lint # 2) Typecheck (catch breaking prop/type changes early) * name: Typecheck run: pnpm -r typecheck # 3) UI tests * name: UI tests run: pnpm --filter @org/ui test # 4) Build UI (ensures exports/types are generated and valid) * name: Build UI run: pnpm --filter @org/ui build # 5) Require a changeset if UI changed * name: Require changeset for UI changes run: pnpm changeset status --since origin/${{ github.base_ref }} # Optional: add a small script that fails if packages/ui changed but no changeset exists. # 6) Public API report/diff (example using API Extractor) * name: API report (detect breaking API) run: pnpm --filter @org/ui api:check # 7) Storybook + visual regression (Chromatic) * name: Chromatic uses: chromaui/action@v11 with: projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} workingDir: packages/ui exitOnceUploaded: true </syntaxhighlight> ==== Public API diff gate (TypeScript contract enforcement) ==== If you want a strong “did we break the public API?” check, a common approach is Microsoft’s API Extractor (it produces an API report you can diff). In packages/ui: ===== package.json scripts ===== <syntaxhighlight lang="json">{ "scripts": { "build": "tsc -p tsconfig.build.json", "api:check": "api-extractor run --local --verbose" }, "devDependencies": { "@microsoft/api-extractor": "^7.0.0" } } </syntaxhighlight> ===== api-extractor.json (minimal) ===== <syntaxhighlight lang="json">{ "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", "mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts", "apiReport": { "enabled": true, "reportFileName": "ui.api.md" }, "docModel": { "enabled": false }, "dtsRollup": { "enabled": false } } </syntaxhighlight> Commit the generated API report (e.g., packages/ui/etc/ui.api.md). Now PRs that change the public API produce a diff that reviewers can treat as “the contract changed.” ==== What this buys them (and what it doesn’t) ==== * If they implement exports boundary + no-deep-import lint + semver/changesets + API diff + component-level visual regression, they’ve created an enforceable contract. * They will still sometimes have “large affected surface area” for foundational components, but now: - Breaking changes become explicit and opt-in (major bumps). - Non-breaking changes have strong automated confidence so you don’t default to full manual regression. If you want, I can add the “fail PR if packages/ui/** changed but no changeset exists” script (it’s ~20 lines in Node) and/or show the “two majors supported” policy operationalized (how to remove old majors without drama).
Summary:
Please note that all contributions to freem are considered to be released under the Creative Commons Attribution-ShareAlike 4.0 (see
Freem:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)