web/web-components/SKILL.md
Architecture and coding rules for single-page applications using web components, BCE layering, lit-html templating, Redux Toolkit state management, and client-side routing. Web standards and web platform first, minimal external dependencies. Use when creating, scaffolding, generating, writing, or reviewing web component applications, custom elements, state management, client-side routing, or frontend BCE architecture. Not for server-side rendering or framework-heavy applications.
npx skillsauth add adambien/airails web-componentsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build or maintain a web component application using $ARGUMENTS. Apply all rules below strictly.
This skill is based on the bce.design quickstarter.
Only these three runtime dependencies are permitted. Do not add others without explicit approval:
Development tooling: Vite (dev server), Rollup (dependency bundling), Playwright (E2E tests).
Structure code using the Boundary Control Entity pattern. Organize by feature module, not by technical layer:
app/src/
├── BElement.js # base class for all custom elements
├── store.js # Redux store configuration
├── app.js # entry point, routing, persistence
├── app.config.js # application metadata (name, version)
├── index.html # single HTML entry point
├── style.css # application styles
├── libs/ # bundled third-party ESM modules
├── [feature-name]/ # one folder per feature module
│ ├── boundary/ # UI web components
│ ├── control/ # actions and dispatchers
│ └── entity/ # reducers and state shape
└── localstorage/
└── control/
└── StorageControl.js # persistence layer
BElement — never extend HTMLElement directlyhtml from lit-htmlview() to return a lit-html template — this is the only required methodextractState(reduxState) to select the relevant state slicethis.state inside view()customElements.define('prefix-name', ClassName)b- prefix for component tag namesimport BElement from "../../BElement.js";
import { html } from "lit-html";
import { deleteItem } from "../control/CRUDControl.js";
class ItemList extends BElement {
extractState({ items: { list } }) {
return list;
}
view() {
return html`
<ol>
${this.state.map(item => html`
<li>${item.label} <button @click="${_ => deleteItem(item.id)}">delete</button></li>
`)}
</ol>
`;
}
}
customElements.define('b-item-list', ItemList);
createAction from Redux Toolkitstore.dispatch() — boundary components call dispatchers, never dispatch directlyDate.now() for generating entity IDs on the clientimport { createAction } from "@reduxjs/toolkit";
import store from "../../store.js";
export const itemUpdatedAction = createAction("itemUpdatedAction");
export const itemUpdated = (name, value) => {
store.dispatch(itemUpdatedAction({ name, value }));
}
export const newItemAction = createAction("newItemAction");
export const newItem = _ => {
store.dispatch(newItemAction(Date.now()));
}
export const deleteItemAction = createAction("deleteItemAction");
export const deleteItem = (id) => {
store.dispatch(deleteItemAction(id));
}
createReducer from Redux Toolkit with builder callbackinitialState as a plain object with sensible defaultsimport { createReducer } from "@reduxjs/toolkit";
import { itemUpdatedAction, deleteItemAction, newItemAction } from "../control/CRUDControl.js";
const initialState = {
list: [],
item: {}
}
export const items = createReducer(initialState, (builder) => {
builder.addCase(itemUpdatedAction, (state, { payload: { name, value } }) => {
state.item[name] = value;
}).addCase(newItemAction, (state, { payload }) => {
state.item["id"] = payload;
state.list = state.list.concat(state.item);
}).addCase(deleteItemAction, (state, { payload }) => {
state.list = state.list.filter(item => item.id !== payload);
});
});
The base class provides automatic Redux integration for all components:
connectedCallback() — subscribes to Redux store, triggers initial renderdisconnectedCallback() — unsubscribes to prevent memory leakstriggerViewUpdate() — extracts state, generates template, renders via lit-htmlview() — abstract, must be overridden to return a lit-html templateextractState(reduxState) — override to select a state slice (default: entire state)getRenderTarget() — override to change render target (default: this)Do not modify the BElement base class unless adding cross-cutting concerns that affect all components.
import { configureStore } from "@reduxjs/toolkit";
import { load } from "./localstorage/control/StorageControl.js";
import { items } from "./items/entity/ItemsReducer.js";
const reducer = { items }
const preloadedState = load();
const config = preloadedState ? { reducer, preloadedState } : { reducer };
const store = configureStore(config);
export default store;
store.subscribe()JSON.stringify / JSON.parse for serializationappName in app.config.jsUser Event → Boundary Component → Control (dispatcher) → Redux Action
→ Entity (reducer) → Store → BElement.triggerViewUpdate()
→ extractState() → view() → lit-html render() → DOM
<a href="..."> for navigation — no programmatic routing unless necessaryimport { Router } from "@vaadin/router";
const outlet = document.querySelector('.view');
const router = new Router(outlet, {});
router.setRoutes([
{ path: '/', component: 'b-item-list' },
{ path: '/add', component: 'b-items' }
]);
index.html entry point with semantic HTML (<main>, <header>, <nav>, <section>, <footer>)<head> to map bare module specifiers to local bundled filesinit.js (non-module) before app.js (module) to set up globals required by dependencies<section> element — router replaces its content with route components<script type="importmap">
{
"imports": {
"lit-html": "./libs/lit-html.js",
"@reduxjs/toolkit": "./libs/redux-toolkit.modern.js",
"@vaadin/router": "./libs/router.js"
}
}
</script>
@container) over media queries for responsive designcontainer-type: inline-size on bodydvh units for viewport height (min-height: 100dvh)b-list { ... })style.csslibs/ directory (not the app root)node_modules as ESM into app/src/libs/index.html map bare specifiers to the bundled local filesnode_modules — always through import maps// rollup.config.js
export default [{
input: [
'./node_modules/lit-html/lit-html.js',
'./node_modules/@vaadin/router/dist/router.js',
'./node_modules/@reduxjs/toolkit/dist/redux-toolkit.modern.mjs'
],
output: { dir: "../app/src/libs", format: "esm" },
plugins: [nodeResolve({ browser: true })]
}]
tests/ directorygetByRole), label selectors (getByLabel), and visibility assertionspressSequentially with delay for simulating realistic text inputcodecoverage/ directory using Monocart Coverage Reports@click, @keyup, @input({ target: { name, value } })event.preventDefault() for form submissionsform.reportValidity() and form.checkValidity()node_modules paths — use import mapsnpx vitetools
Generic, composable Java 25 code conventions — modern syntax, code style, naming, visibility, structure, methods, streams, exceptions, and documentation rules that apply across all Java contexts (single-file scripts, CLI apps, MicroProfile/Jakarta EE servers, libraries). Technology-neutral within the Java world; meant to be composed with context-specific skills (e.g. `java-cli-script`, `java-cli-app`, `microprofile-server`, `bce`). Use when writing, generating, or reviewing Java code anywhere the composed skill does not already specify style. Triggers on "Java conventions", "Java style", "Java code style", "modern Java", "Java 25", "idiomatic Java", or any request to write or review Java code where context-specific skills do not already cover style.
tools
Create zero-dependency, single-file executable Java scripts for system-wide use via PATH. Use when asked to create a single-file Java shell script, system utility, PATH-installed Java tool, or shebang-launched Java program without the .java extension. Triggers on "Java script", "Java utility", "PATH script", "system script", or requests for single-file Java programs installed in /usr/local/bin or similar PATH directories. Not for multi-file Java applications — use java-cli-app for those.
development
Architecture and coding rules for long-running Java MicroProfile / Jakarta EE server applications — BCE layering, business components (BC), JAX-RS resources, CDI, JSON-P, testing (unit/integration/system), and Maven project structure. Use when creating, generating, scaffolding, writing, or reviewing code, resources, entities, boundaries, or business components in MicroProfile server projects. Not for serverless deployments.
tools
Create and maintain multi-file Java 25 CLI applications packaged as executable JARs with zb (Zero Dependencies Builder). Use when asked to create a Java CLI application, a CLI project with multiple source files, or an executable JAR. Triggers on "Java CLI app", "CLI application", "multi-file Java", "executable JAR", "zb build", or requests for Java programs that need multiple source files or JAR packaging. Not for single-file scripts — use java-cli-script for those.