Executable reference for AI agents working in this repo. Follow these rules literally.
Answer these questions in order. Stop if you can't answer one.
- Which element is changing? Identify the folder in
/elements/(e.g.,elements/map). - Is this a single-element change? If the request touches multiple elements, state that you will split it into atomic PRs/commits.
- New/changed attributes or properties?
- Attributes: strings, numbers, booleans (HTML compatible).
- Properties: Objects, Arrays, Functions (Complex types).
- What is the minimum change? State it in one sentence.
- Which existing pattern does this follow? (e.g., "following logic extraction pattern in
src/methods/").
If any answer is "I don't know," stop. Ask a clarifying question. Do not guess.
EOxElements is an npm workspaces monorepo containing multiple published web component packages under the @eox/ scope, plus a shared utilities package (@eox/elements-utils) and an MCP server (@eox/elements-mcp-server).
- Package name:
@eox/elements(private, not published) - Module system: ESM (
"type": "module") - Node requirement:
>=24.0.0(enforced viaengine-strict=truein.npmrc) - Security Hardening:
ignore-scripts=trueis enabled in.npmrc. This means:postinstallscripts (like Cypress binary download) are skipped. Runnpx cypress installmanually for local testing.prepackscripts are skipped. When publishing or testing packaging, manually runnpm run types:generate.
- Package Quarantine:
min-release-age=7(days) is set in.npmrcto prevent the use of brand-new, potentially unverified package versions. - Component framework: Lit (v3.2+)
- Language: JavaScript with JSDoc type annotations (NOT TypeScript source files)
| Category | Technology |
|---|---|
| Component framework | Lit 3.2+ (LitElement) |
| Language | JavaScript + JSDoc (type-checked by TypeScript) |
| Bundler | Vite (library mode per element) |
| Type checker | TypeScript (checkJs mode, no .ts source) |
| Test runner | Cypress (component + E2E) |
| Component mounting | cypress-lit |
| Coverage | Istanbul (vite-plugin-istanbul) + NYC |
| Storybook | @storybook/web-components-vite (v10) |
| Linting | ESLint (flat config) + @eox/eslint-config |
| Formatting | Prettier |
| Release | release-please (per-package automated releases) |
| CI/CD | GitHub Actions |
| Design system | @eox/ui (CSS variables + fonts) |
Every element follows a consistent directory layout:
elements/<name>/
├── package.json # @eox/<name>, main: ./src/main.js
├── vite.config.js # Vite library build config
├── tsconfig.build.json # TypeScript config for .d.ts generation
├── src/
│ ├── main.js # Main LitElement class + customElements.define()
│ ├── types.ts # TypeScript type definitions (if needed)
│ ├── style.js # Base/functional CSS (always applied)
│ ├── style.eox.js # Branded/themed CSS (unless `unstyled`)
│ ├── enums/ # Constants and defaults (e.g. index.js, frozen objects)
│ ├── helpers/ # Pure utility functions
│ ├── methods/ # Extracted component logic (e.g., domain/index.js)
│ └── components/ # Sub-components (if decomposed)
├── stories/ # Storybook stories (*.stories.js)
└── test/ # Cypress component tests (*.cy.js)
├── cases/ # Test case implementations (extracted logic)
└── fixtures/ # JSON/image test fixtures
Every element follows this structure in src/main.js:
import { LitElement, html, nothing } from "lit";
// ... imports
/**
* @element eox-example
* @fires {CustomEvent} change - Fired when the value changes
*/
export class EOxExample extends LitElement {
static get properties() {
return {
prop: { attribute: false, type: Array },
unstyled: { type: Boolean },
};
}
constructor() {
super();
this.prop = [];
this.unstyled = false;
}
// Lifecycle and Logic...
render() {
return html`
<style>
${style} ${!this.unstyled && styleEOX}
</style>
<div>...</div>
`;
}
}
customElements.define("eox-example", EOxExample);- No TypeScript Code: Use JSDoc for types. Do NOT use TypeScript decorators (
@property,@state) or.tsfiles for logic. TypeScript is used only for type definitions (types.ts,@typedef). - Reactive Properties: Always use
static get properties()orstatic properties = {}. Lit decorators are NOT used anywhere in the codebase. Useattribute: falsefor complex Object/Array types. - Method Delegation (Most Important Pattern): Extract business logic to standalone functions in
src/methods/<domain>/directories.- Functions MUST receive the component instance (
this) as a parameter, e.g.,export function setCenterMethod(center, EOxMap) { ... }. - The component class calls these from its setters/lifecycle, passing
this. - Re-export methods via barrel
index.jsfiles.
- Functions MUST receive the component instance (
- Enums & Constants: Do NOT use TypeScript enums. Constants live in
src/enums/as frozen JavaScript objects withSCREAMING_SNAKE_CASEnaming. - Projections: For vector sources with a specific projection (e.g., GeoJSON in EPSG:3035), ensure the projection is registered globally. Prefer using
registerProjectionwith a direct proj4 definition string for stability in stories, orregisterProjectionFromCodefor dynamic environments. In the layer definition, use theformatobject to specifydataProjection. - Inter-Component Communication: Components depending on
<eox-map>use a standardizedforproperty (a CSS selector defaulting to"eox-map"). InfirstUpdated(), callgetElement(this.for)from@eox/elements-utilsto resolve the reference, even across shadow boundaries. Address re-resolution on property change inupdated(). - Styling:
- EOxUI First: This repository uses EOxUI. Prefer using proper HTML structure and EOxUI classes over writing custom CSS.
- Minimal Custom CSS: Only write custom CSS if the requirement cannot be solved using EOxUI.
- Common Styles: Use
addCommonStylesheet()from@eox/elements-utilsin all elements. - Isolation: Prefer
cssliteral for component-specific styles if absolutely necessary.
- Shadow DOM: Use
createRenderRoot() { return this.noShadow ? this : super.createRenderRoot(); }if needed. Usebubbles: true, composed: truewhen events need to cross shadow DOM boundaries. - Testing Structure:
.cy.jsfiles definedescribe/itstructure only. Test logic MUST be extracted into standalone functions insidetest/cases/and re-exported via barrierindex.jsfiles. There is no Jest/Vitest. Web components are mounted viacypress-lit.
EOxElements is integrated with an MCP Server. Documentation is code.
- Events: You MUST use
@firesin the class JSDoc AND include a JSDoc comment directly above thedispatchEventcall for it to be indexed. - Methods: Public methods must have full JSDoc (params, return types).
- Custom Elements Manifest: Ensure your JSDoc is compatible with
@custom-elements-manifest/analyzer.
- Every new feature MUST have a story in
[element].stories.js. - Stories must include a fully functional code example in the description (rendered in Storybook Docs).
- JSDoc at Story Level: Use JSDoc to explain the specific use case the story demonstrates. The text must include enough information so that a coding agent can find and use the story as an example for including the EOxElement in a downstream application.
Touch only what the request requires. Do not refactor adjacent components.
Before starting, state your plan:
- [Logic] -> verify: [run component test]
- [UI/Styles] -> verify: [npm run lint:fix]
- [Docs/Stories] -> verify: [check JSDoc/story presence]
After every implementation, you MUST run:
- Component Tests: Run tests from the root level for the specific element workspace.
- Run all component tests (if necessary):
npm run test:component - Run tests for a specific element (preferred):
npx cypress run --component --spec "elements/[element]/test/*.cy.js"
- Run all component tests (if necessary):
- Linting: Run
npm run lint:fixfrom the root to fix style issues. - Type Checking: Run
npm run typecheckto ensure type safety. - Formatting: Run
npm run formatto ensure consistent code style.
We use release-please. Formatting errors here will break the release pipeline.
- PR Title: Must be in Conventional Commit format (e.g.,
feat: add tooltip property). It is not needed to use the element name in the title scope (e.g.,fix(drawtools): ...is not required) sincerelease-pleasehandles the changelog/release of the element automatically. Instead, use standard scopes or no scopes (e.g.,fix: fixed a thingorfix(style): ...). - Commit Message: Same as PR Title. Since we squash, the PR title becomes the commit.
- One Element Per PR: Never mix changes for e.g.
mapandlayercontrolin one PR. - One Feature/Fix Per PR: Never mix different features or fixes into one PR (even if they concern the same element). Each PR is squashed and represents exactly one entry in the changelog.
- Lint failure: Read the error message literally. Fix only the flagged line. Re-run
npm run lint:fix. - Typecheck failure: Ensure types are defined in
types.tsorsrc/main.jsusing@typedef. - Event not showing in Storybook: Ensure the
dispatchEventhas a JSDoc block immediately above it.
- I changed only ONE element folder.
- Every property/attribute has a JSDoc description.
- Every new event has an
@firestag AND a JSDoc block abovedispatchEvent. - Functional story added to
stories/with a code example. -
npm run lint:fix,typecheck, andformatpassed. - PR title/commit message follows
type(scope): messageformat. - I have verified that
addCommonStylesheet()is used for common styles.
- Do NOT use TypeScript source files — write
.jswith JSDoc annotations. Only use.tsfor type definitions. - Do NOT use Lit decorators — use
static get properties()orstatic properties = {}instead. - Do NOT create custom base classes or mixins — extend
LitElementdirectly and use the method extraction pattern for shared logic. - Do NOT use centralized state management — use LitElement's reactive properties and custom events.
- Do NOT forget
addCommonStylesheet()— call it instyle.eox.jsfor consistent global styles. - Do NOT skip the
unstyledproperty — every component must support disabling branded styling. - Do NOT register custom elements anywhere other than the bottom of
main.js. - Do NOT use default exports for component classes — use named exports.
- Always use barrel
index.jsfiles inhelpers/,methods/, andenums/directories. - Always pass the component instance as a parameter to extracted method functions (never rely on
thisbinding in separate files).