Skip to content

Latest commit

 

History

History
356 lines (281 loc) · 16.6 KB

File metadata and controls

356 lines (281 loc) · 16.6 KB

AGENTS.md - PIE Design System

This document provides guidance for AI agents working with the PIE (Principles for Interfaces and Experiences) Design System codebase.

Project Overview

PIE is Just Eat Takeaway's global design system. This monorepo contains:

  • Web Components - Framework-agnostic components built with Lit 3
  • Documentation - The pie.design documentation site
  • Storybook - Component playground, web engineering documentation and testing environment
  • Tools - Shared tooling packages for component development

Repository Structure

pie/
├── apps/
│   ├── pie-docs/          # Documentation site (pie.design)
│   └── pie-storybook/     # Storybook playground (webc.pie.design)
├── packages/
│   ├── components/        # Web Component packages (pie-button, pie-modal, etc.)
│   │   ├── pie-webc/      # Umbrella package that re-exports all components
│   │   └── pie-webc-core/ # Shared base class, mixins, decorators, and utilities
│   └── tools/             # Shared tooling (pie-css, pie-icons, etc.)
├── configs/               # Shared configuration packages (vite, playwright, cem)
└── .changeset/            # Changeset files for versioning

Technology Stack

  • Runtime: Node.js 22 or 24 (specified in package.json engines). Versions are pinned via Mise (see mise.toml in the repo root). Install Mise to have node versions switched automatically.
  • Package Manager: Yarn 4.x (exact version pinned in root package.json via packageManager)
  • Monorepo: Turborepo
  • Build Tool: Vite
  • Web Components: Lit 3.x (exact version pinned in root package.json resolutions.lit)
  • TypeScript: TypeScript 5.x (exact version defined in root package.json devDependencies.typescript)
  • Testing:
    • Playwright (browser tests)
    • Vitest (unit tests)
    • Percy (visual regression)

Component Structure

Each component follows this structure:

component-name/
├── dist/                          # Built output
├── src/
│   ├── component-name.scss        # Component styles
│   ├── defs.ts                    # TypeScript type definitions
│   ├── defs-react.ts              # React-specific types
│   └── index.ts                   # Component entry point
├── test/
│   ├── accessibility/             # Accessibility tests
│   ├── component/                 # Browser tests (Playwright)
│   ├── helpers/page-object/       # Page object test helpers
│   └── visual/                    # Visual regression tests
├── package.json
├── tsconfig.json
├── vite.config.js
├── playwright-lit.config.ts        # Playwright config (browser/a11y tests)
├── playwright-lit-visual.config.ts # Playwright config (visual regression tests)
└── custom-elements-manifest.config.mjs

Key Conventions

Component Development

  1. Use Lit 3 - All components extend PieElement from @justeattakeaway/pie-webc-core
  2. Type Safety - Define types in defs.ts, use TypeScript strictly
  3. Styling - Use SCSS files imported as ?inline in component files
  4. Design Tokens - Use @justeattakeaway/pie-css token APIs for colours, spacing, etc. (sourced from @justeattakeaway/pie-design-tokens).
  5. Mixins - Use mixins from pie-webc-core (e.g., FormControlMixin, RtlMixin, DelegatesFocusMixin)

Testing Philosophy

Primary Testing Strategy: Browser Tests

  • Test components in real browsers using Playwright, not jsdom
  • Test user-facing behaviour, not implementation details
  • Use Storybook stories as test fixtures
  • Focus on accessibility, form behaviour, and user interactions

Test Types:

  • Browser Tests (test/component/*.spec.ts) - Primary testing method
  • Accessibility Tests (test/accessibility/*.spec.ts) - A11y checks via axe-core
  • Visual Tests (test/visual/*.spec.ts) - Percy visual regression
  • Unit Tests - Only for small utilities, not components

Test Naming:

  • Use test.describe blocks: Component name → Feature → Specific behaviour
  • Test names: should [do something], should [do something] when [condition]
  • Example: should submit form when Enter key is pressed

Git Workflow

Branch Naming:

dsw-123-my-feature

Where dsw-123 is the Jira ticket ID.

Commit Messages: The repo follows committizen standards for commits. Make sure to follow this format:

Format:

type(scope): DSW-123 your message

Commit Types:

  • feat - New feature or functionality
  • fix - Bug fix
  • chore - Build/dependency changes (e.g. webpack, eslint, package.json)
  • ci - CI configuration changes
  • docs - Documentation only changes
  • format - Code formatting / linting fixes (no logic change)
  • performance - Performance improvements
  • refactor - Code refactoring (neither a fix nor a feature)
  • release - Version release
  • revert - Reverting a previous commit
  • cosmetic - UI styling changes (design tokens, Sass, SVGs)
  • test - Adding missing tests
  • wip - Work in progress (use for incomplete commits)

Commit scope The scope part of each commit message should be the package name changed. e.g. pie-button

Pull Requests:

  • Use the PR template in .github/pull_request_template.md
  • The PR title must follow the same type(scope): TICKET-123 title format as commit messages — this is validated automatically by Danger JS
  • Include changeset for consumer-facing changes
  • Test in PIE Aperture before merging
  • Get reviews from #help-designsystem Slack channel

Automated PR Checks (Danger JS)

A dangerfile.js runs automatically on every PR and will fail it if any of the following are violated. Be aware of these before raising a PR:

  • PR title format — must match type(scope): TICKET-123 title (ticket ID cannot be all zeros)
  • Changeset format — entries must follow [Category] - {Description} (e.g. [Fixed] - Corrected focus ring colour)
  • README structure — component READMEs must contain all of the following sections:
    • npm badge (https://img.shields.io/npm/v/@justeattakeaway/...)
    • ## Table of Contents
    • ## Documentation with sub-sections: ### Properties, ### Slots, ### Events, ### CSS Variables
    • ## Usage Examples
    • ## Questions and Support
    • ## Contributing
  • PR checklist — all Author Checklist items must be checked (or explicitly moved to the "Not-applicable Checklist items" section). Leaving unchecked boxes elsewhere will fail the PR.

Versioning & Changesets

When to create a changeset:

  • Any change that affects consumers of the package
  • New features, bug fixes, breaking changes
  • Run: yarn changeset

Changeset Format: Entries must be prefixed with a category in square brackets, followed by a dash and a description:

[Added] - New `isLoading` prop on pie-button
[Changed] - Renamed `size` prop values
[Fixed] - Corrected focus ring colour in high contrast mode
[Removed] - Dropped deprecated `outline-inverse` variant

Snapshot Releases:

  • Comment /snapit on PR to trigger snapshot release
  • Or /test-aperture to create Aperture PR automatically

Adding and Running Tools

Never use npx — it can silently download and execute unverified packages, bypassing version locking and introducing supply chain risk.

Adding a new external tool

Install it as a devDependency in the appropriate package.json so the version is pinned in yarn.lock and monitored by Dependabot:

yarn add -D <package-name>

Adding a new monorepo-internal bin script

Declare it in the root package.json "bin" field, not in the source package. Yarn Berry's run -T only resolves binaries from packages the root workspace explicitly owns — declaring the bin at the root guarantees correct resolution:

"bin": {
  "my-tool": "./packages/tools/my-package/index.js"
}

Then run yarn install to link the binary.

Invocation patterns

There are two contexts, each with its own form:

Context Form Example
package.json scripts run -T <bin> "build:react-wrapper": "run -T build-react-wrapper"
Shell contexts (CI steps, husky hooks) yarn <bin> yarn commitlint --edit ${1}

run -T is a Yarn script shorthand processed before the shell sees the command. In shell contexts (CI run: steps, husky hooks), use yarn <bin> instead.

Exception: when a root package.json script shares a name with a binary (e.g. changeset), use yarn exec <bin> in shell contexts to bypass script lookup and invoke the binary directly.

Example: adding a new tool end-to-end

  1. Install the package: yarn add -D my-tool
  2. If it is a monorepo-internal script (not an npm package): add it to root package.json "bin" and run yarn install
  3. Invoke it in package.json scripts with run -T my-tool
  4. Invoke it in CI steps or husky hooks with yarn my-tool

Spawning Commands from Scripts

When a script runs a command via child_process, pass the command and arguments as an array — prefer execFileSync/spawn (e.g. execFileSync('git', ['show', ref + ':' + file])) over execSync with an interpolated string. The array form handles spaces/special characters in dynamic values (paths, refs, glob results) correctly. Reserve the plain string form for fully static commands that interpolate nothing.

Common Commands

Development

  • Install dependencies: yarn install
  • Watch a component during development: yarn watch --filter=@justeattakeaway/pie-{component-name}
  • Run Storybook: yarn dev --filter=@justeattakeaway/pie-storybook
  • Run Storybook in browser-testing mode: yarn dev:testing --filter=@justeattakeaway/pie-storybook
  • Build all packages: yarn build

Testing

  • Run once before browser tests (or after Playwright config changes): yarn test:browsers-setup --filter=@justeattakeaway/pie-{component-name}
  • Run browser tests for a component: yarn test:browsers --filter=@justeattakeaway/pie-{component-name}
  • Run all tests: yarn test
  • Run visual tests: yarn test:visual --filter=@justeattakeaway/pie-{component-name}

Linting

  • Lint JS/TS scripts: yarn lint:scripts
  • Lint styles: yarn lint:style
  • Fix JS/TS lint issues: yarn lint:scripts:fix
  • Fix style linting issues: yarn lint:style:fix

Code Style Guidelines

TypeScript

  • Use strict TypeScript
  • Define component props in defs.ts
  • Use @property decorators for Lit properties
  • Use @validPropertyValues for prop validation
  • Use @requiredProperty for props that must always be provided
  • Export types from defs.ts for consumers

Web Components

  • Extend PieElement from @justeattakeaway/pie-webc-core
  • Use @safeCustomElement decorator
  • Implement proper lifecycle methods (connectedCallback, disconnectedCallback, updated)
  • Use FormControlMixin for form components
  • Use DelegatesFocusMixin when the component needs to delegate focus to an internal element
  • Ensure all components support RTL; use RtlMixin when programmatic JS awareness of direction changes is needed
  • For public type exports, see TypeScript guidelines.
  • React wrappers are auto-generated during build (build:react-wrapper)
    • Generated files are in src/react.ts (untracked)
    • Uses @justeattakeaway/pie-wrapper-react

Event Handling

Prefer native platform events where possible (for example Event/InputEvent) and avoid custom events unless there is a concrete requirement for a custom payload shape or cross-boundary behaviour.

  • If a native event is sufficient, dispatch/forward the native event.
  • If you need a PIE custom event API, use dispatchCustomEvent(element, 'pie-event-name', detail) from pie-webc-core rather than constructing CustomEvent manually.
  • If you need to forward a non-composed native event out of shadow DOM, use wrapNativeEvent(event) from pie-webc-core.

Styling

  • Use SCSS with design tokens (CSS variables where possible)
    • Design tokens are defined in the @justeattakeaway/pie-design-tokens package
    • DON'T invent token names
  • Classnames should follow these naming conventions:
    • Prefixes
      • Objects = .o- (components tied to HTML tags, e.g. .o-btn)
      • Components = .c- (everything else, e.g. .c-rating)
      • Utilities = .u- (reusable helpers, e.g. .u-showAboveMid)
      • Layout = .l- (global layout, e.g. .l-container)
    • Camel-case descriptors when multi-word (e.g. .c-myComponentName)
    • Child elements use a single hyphen - (e.g. .o-form-controlGroup)
    • Modifiers use a double hyphen -- (e.g. .o-btn--primary). Modifiers extend a base class; always apply both (e.g. class="o-btn o-btn--primary")
    • State uses .is- or .has- prefix for JS-driven state changes (e.g. .is-active, .has-error)
  • Use logical properties (e.g. margin-inline-start not margin-left)
  • Import styles as ?inline in component files
  • Do not rely on :host([attribute="value"]) selectors, instead, do prefer to create a new class and map the prop to it using Lit classMap.

Documentation

  • Write all documentation in British English and use British English spelling consistently.

Accessibility

  • Use semantic HTML
  • Support keyboard navigation
  • Include ARIA attributes where needed
  • Code must be compatible with screen readers
  • Follow WCAG guidelines

Important Notes

Dependencies

  • Lit: 3.x (exact version pinned in root package.json resolutions.lit)
  • pie-webc-core: Provides shared base class, mixins, decorators, and utility functions
  • pie-webc: Umbrella package — consumers install this to get all components at once
  • pie-design-tokens: Design system tokens
  • pie-css: Shared CSS utilities and reset styles
  • element-internals-polyfill: Required for form-associated components; include as a dependency in any component using FormControlMixin

Monorepo Management

  • Use Turborepo for task orchestration
  • Tasks are defined in turbo.json
  • Use --filter flag to target specific packages
  • Build dependencies are handled automatically

Turborepo Pipeline

Tasks in turbo.json have defined dependencies that run automatically. Key relationships to be aware of:

  • build depends on ^build (all upstream packages) and build:react-wrapper
  • build:react-wrapper depends on create:manifest (the custom elements manifest must exist first)
  • test:browsers depends on build (component must be built before tests run)
  • test:browsers-setup must be run manually the first time or after config changes; it copies Playwright fixture files into the component directory

This means you generally only need to run the top-level command (e.g. yarn test:browsers) and Turborepo will handle the dependency chain. However, test:browsers-setup is a prerequisite that must be run at least once.

Resources

When Making Changes

  1. Check existing patterns - Look at similar components (e.g., pie-button)
  2. Follow the structure - Use generator-pie-component (or copy from existing components)
  3. Test in Storybook - Verify visually before writing tests
  4. Prioritise browser tests - Browser tests are primary, not unit tests
  5. Use page objects - Follow the BasePage / [Component]Page / [Component]Component pattern
  6. Create changeset - If change affects consumers, using the [Category] - description format
  7. Update README - Component README becomes the Storybook overview and must meet the required structure enforced by Danger JS
  8. Test in Aperture - Use /test-aperture once per PR only (IMPORTANT – rerunning this command is destructive)

Avoid

  • Don't use jsdom for component tests - use real browsers
  • Don't skip browser tests in favour of unit tests
  • Don't change component status without approval
  • Don't use a commit message that does not match type(scope): TICKET-123 your message
  • Don't skip changesets for consumer-facing changes
  • Don't create components manually - use the generator
  • Don't dispatch custom events manually with new CustomEvent(...) - use dispatchCustomEvent from pie-webc-core
  • Don't write a README without all required sections - Danger JS will fail the PR
  • Don't leave unchecked PR checklist items outside the "Not-applicable" section - Danger JS will fail the PR
  • Don't run browser tests without first running test:browsers-setup