Imported from lnsy-dev/pochade-js (
template/AGENTS.md). Install upstream withnpx skills add lnsy-dev/pochade-js --skill template. Copyright stays with the author.
Agent Conventions for Pochade-JS Projects
This file governs all code in this directory and its subdirectories.
Technology Stack
- JavaScript: Vanilla ES2020+ (no frameworks)
- CSS: Standard CSS with variables (no CSS-in-JS, no Shadow DOM)
- Build Tool: Webpack 5 with SWC transpilation
- Custom Elements: dataroom-js (extends HTMLElement)
- Workers: Web Workers with custom inline bundling
- WebAssembly: C++ via Emscripten, Rust via wasm-pack
- Testing: see the Testing section below
Code Style
Comments
Use DocBlock style comments for all classes, methods, and exported functions:
/**
* Brief description.
*
* @param {string} paramName Description
* @returns {number} Description
*/
Use inline // comments for implementation logic.
Custom Elements
This is the core architecture of every Pochade-JS project: we build with custom HTML elements, and those elements talk to each other by setting attributes on one another.
- An element's attributes ARE its public API. Another component should never reach into a component's internals or call its methods — it sets an attribute on the element instead:
document.querySelector('my-component').setAttribute('value', '42') - React to attribute changes by declaring
static get observedAttributes()and implementingattributeChangedCallback(name, oldValue, newValue)(or reading attributes ininitialize()) - Keep state visible in markup: when internal state changes, reflect it back to an attribute so other elements (and users) can observe it
- Custom events (
this.event(...)) may announce that something happened, but data flows between elements through attributes
import DataroomElement from 'dataroom-js';
class MyComponent extends DataroomElement {
async initialize() {
// Component setup
}
}
if (!customElements.get('my-component')) {
customElements.define('my-component', MyComponent);
}
Rules:
- Element names MUST contain a hyphen
- NEVER use Shadow DOM
- NEVER embed CSS in JavaScript
- Create CSS in
styles/<component-name>.cssand import inindex.css - Always include ARIA attributes on rendered elements (see Accessibility section)
Accessibility (ARIA)
Directive: Every custom element you create MUST be accessible. Add ARIA attributes and validate your HTML.
- Give every interactive custom element an appropriate
roleattribute - Label every element with
aria-labeloraria-labelledby(never rely on visual position alone) - Reflect component state in ARIA state attributes (
aria-expanded,aria-checked,aria-disabled,aria-live, ...) - Keep ARIA attributes up to date whenever the component's state changes
- Use semantic HTML elements first (
button,nav,main, ...); reach for ARIA only when semantics are not enough - Run the HTML validator before considering any change complete:
npm run validate:html
The validator must pass with zero errors. Fix invalid markup, duplicate attributes, and missing ARIA labels it reports.
Web Workers
Always use this exact syntax:
const worker = new Worker(new URL('./my-worker.js', import.meta.url));
Never use string paths: new Worker('./my-worker.js') — bundlers cannot trace them.
WebAssembly
C++ (Emscripten)
- Place source in
src/wasm/cpp/<name>.cpp - Use
EMSCRIPTEN_KEEPALIVEon exported functions - Build with
npm run build:wasm:cpp - Load glue module with dynamic
import() - Use
cwrap()to create typed JS functions
Rust (wasm-pack)
- Place crate in
src/wasm/rust/<crate-name>/ - Use
#[wasm_bindgen]on exported functions - Build with
npm run build:wasm:rust - Load pkg module with dynamic
import() - Call
await module.default()before using exports
Testing
Directive: Write and run tests for every feature you add or change. Use each suite below for its stated purpose, keep all of them green, and add a matching test whenever you introduce new behavior.
E2E Tests (Playwright)
- Use
@playwright/testfor all E2E tests - Place tests in
tests/e2e/*.spec.js - Run with
npm test; debug withnpm run test:ui; verify the production build withnpm run test:prod - Use
page.locator()for element selection - Use
page.evaluate()for testing custom events - Use 15-second timeouts for wasm-dependent assertions
- Every user-facing feature MUST have an E2E test; run the suite before considering any change complete
Behavioral Tests (BDD-style, Playwright)
- Place tests in
tests/behavioral/*.feature.spec.js - Run with
npm test(they execute alongside the E2E suite) - Structure every scenario with explicit Given / When / Then steps via
test.step()— seetests/behavioral/counter.feature.spec.js - Describe behavior from the user's point of view; assert only on what the user can observe, never on internal state
- Write the scenario first, then implement until it passes
Unit Tests (Vitest)
- Use
vitest; place tests intests/unit/*.test.js - Run with
npm run test:unit - Unit tests target pure logic only — no DOM, no dev server. Extract logic from components into
src/*-logic.jsmodules (seesrc/counter-logic.js) and test those - Assert exact values, not just definedness — weak assertions produce surviving mutants
- New logic MUST ship with unit tests in the same change
Mutation Tests (Stryker)
- Run with
npm run test:mutation - Stryker mutates the files listed in
stryker.config.js(mutatearray) and reruns the Vitest unit suite for each mutant - Only add a file to
mutateonce it has real unit test coverage - Investigate every surviving mutant: either strengthen the unit test to kill it or document why it is equivalent
- Keep the mutation score above the
breakthreshold instryker.config.js
State Management
- Attribute-first communication: components talk to each other by setting attributes on each other's elements; treat attributes as the public API of every custom element (see Custom Elements section)
- Use component instance properties (
this.propertyName) for private, internal state only - Emit custom events to announce that something happened via
this.event('name', detail)— but pass the data itself through attributes - Listen to events via
this.on('name', callback)orthis.once('name', callback)
HTTP Requests
- Use
this.getJSON(url)for simple GET requests to JSON endpoints - Use
this.call(endpoint, body)for POST requests with auth/timeout support - Always wrap in
try/catchfor error handling
File Organization
| Directory | Purpose |
|---|---|
src/ |
JavaScript modules and components |
src/wasm/ |
WebAssembly source files and binaries |
styles/ |
CSS files (one per component or concern) |
| tests/ | Test files (see Testing section) |
| scripts/ | Build-time transformation scripts |
| assets/ | Static files (images, fonts, etc.) |
Prohibited Patterns
- ❌ TypeScript
- ❌ React/Vue/Angular/Svelte
- ❌ Shadow DOM
- ❌ CSS-in-JS (styled-components, emotion, etc.)
- ❌ Inline styles in JavaScript
- ❌ Framework-specific state managers (Redux, Pinia, etc.)
- ❌ jQuery or similar DOM wrappers
- ❌
new Worker('./relative-path.js')(usenew URL(..., import.meta.url))