@design.estate/wcctools
🛠️ Web Component Development Tools — A powerful framework for building, testing, documenting, and recording web components
Overview
@design.estate/wcctools provides a comprehensive development environment for web components, featuring:
- 🖥️
wcctools dev— One command bundles your catalogue with live reload and serves it in the wcctools shell, on this machine only unless you expose it - 📸 Headless Captures —
wcctools screenshotand the dev server's typed API screenshot any demo at any viewport and theme, and observe scripted interactions frame by frame - 🎨 Interactive Component Catalogue — Live preview with customizable sidebar sections
- 🔧 Real-time Property Editing — Modify component props on the fly with auto-detected editors
- 🌓 Theme Switching — Test light/dark modes instantly
- 📱 Responsive Viewport Testing — Phone, phablet, tablet, and desktop views in a frame of the exact width, so width media queries respond
- 🎬 Screen Recording — Record component demos with audio, trimming, and MP4/WebM export
- 🧪 Advanced Demo Tools — Post-render hooks for interactive testing
- 📂 Section-based Organization — Group components into custom sections with filtering and sorting
- 🚀 Zero-config Setup — TypeScript and Lit support out of the box
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Installation
# Using pnpm (recommended)
pnpm add -D @design.estate/wcctools
# Using npm
npm install @design.estate/wcctools --save-dev
Captures run in a headless Chrome or Chromium found on the PATH (google-chrome, chromium or chromium-browser), driven by Puppeteer. pnpm asks whether Puppeteer's install script may run; without it, Puppeteer downloads no browser of its own and the one on the PATH is used:
# pnpm-workspace.yaml
allowBuilds:
puppeteer: false
Migrating from @design.estate/dees-wcctools
@design.estate/dees-wcctools is now published as @design.estate/wcctools. Versions continue from 5.0.0 and the API is the same: swap the dependency and the import specifier.
pnpm remove @design.estate/dees-wcctools
pnpm add -D @design.estate/wcctools
// before: import { setupWccTools } from '@design.estate/dees-wcctools';
import { setupWccTools } from '@design.estate/wcctools';
A catalogue still on a 4.x or older release also follows the 5.0.0 changes: setupWccTools() takes only the config object and DeesDemoWrapper moved to @design.estate/dees-element/demotools.
Migrating to 6.0.0
6.0.0 splits the catalogue into two documents. The shell (sidebar, properties panel, recorder) is prebuilt into the package and served by the new wcctools dev command; your catalogue bundle runs as the preview document in a same-origin iframe that is exactly as wide as the selected viewport. Your html/index.ts and html/index.html stay as they are.
- Keep
@design.estate/wcctoolsas a devDependency at^6.0.0. - Replace the
watchscript:"watch": "wcctools dev"(it reads the same@git.zone/tswatchconfiguration from.smartconfig.json, or uses tswatch'selementpreset). - Open the address
wcctools devprints (http://localhost:<port>/wcctools/).
Opened outside wcctools dev (for example from plain tswatch), the catalogue bundle shows a notice that names the command instead of the catalogue UI. RecorderService, WccRecordButton and WccRecordingPanel are no longer exported: recording lives in the shell.
Migrating to 7.0.0
7.0.0 locks the dev server down, because its typed API now drives a headless browser:
wcctools devlistens on127.0.0.1only. To open the catalogue from another machine, start it with--host 0.0.0.0(every interface; the machine's own addresses are then allowed) or--host <address>, and allow every further name you browse it under with--allowed-host <name>. Exposed this way, every machine that can reach the port can use the shell and the capture API: theHostandOriginchecks below keep web pages in your browser from using the server, they do not authenticate other machines.- Requests for a
Hostthe server does not serve are refused with403and a message naming the--allowed-hostto add. - Typed requests and other state-changing requests, and WebSocket connections, are admitted only from the server's own origin; a request without an
Originheader only from this machine. - The preview no longer sends CORS headers: other origins cannot read the catalogue bundle.
Nothing changes for a catalogue opened on http://localhost:<port>/wcctools/.
Quick Start
1. Create Your Component
import { DeesElement, customElement, html, css, property } from '@design.estate/dees-element';
@customElement('my-button')
export class MyButton extends DeesElement {
// Define a demo for the catalogue
public static demo = () => html`
<my-button .label=${'Click me!'} .variant=${'primary'}></my-button>
`;
@property({ type: String })
accessor label: string = 'Button';
@property({ type: String })
accessor variant: 'primary' | 'secondary' = 'primary';
public static styles = [
css`
:host {
display: inline-block;
}
button {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
}
button.primary {
background: #3b82f6;
color: white;
}
button.secondary {
background: #6b7280;
color: white;
}
`
];
public render() {
return html`
<button class="${this.variant}">${this.label}</button>
`;
}
}
2. Set Up Your Catalogue
// html/index.ts
import { setupWccTools } from '@design.estate/wcctools';
// Import your components
import * as elements from './components/index.js';
import * as views from './views/index.js';
import * as pages from './pages/index.js';
// Initialize with sections-based configuration
setupWccTools({
sections: [
{
name: 'Pages',
type: 'pages',
items: pages,
},
{
name: 'Views',
type: 'elements',
items: views,
icon: 'web',
},
{
name: 'Elements',
type: 'elements',
items: elements,
sort: ([a], [b]) => a.localeCompare(b),
},
],
});
3. Create an HTML Entry Point
wcctools dev bundles html/index.ts to dist_watch/bundle.js and serves html/index.html as the preview document:
<!DOCTYPE html>
<html>
<head>
<title>Component Catalogue</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" src="/bundle.js"></script>
</head>
<body style="margin: 0; padding: 0;">
</body>
</html>
4. Run the Catalogue
pnpm exec wcctools dev # port from the tswatch configuration, else 3002
pnpm exec wcctools dev --port 0 # any free port
pnpm exec wcctools dev --host 0.0.0.0 --allowed-host devbox.lan # reachable from the network as devbox.lan
It prints the shell's address, for example wcctools dev: http://localhost:3002/wcctools/. Saving a source file rebuilds the bundle and reloads only the preview frame; the shell keeps its state. When a bundle fails, the shell shows bundle failed with the bundler's message over the preview until the next run of that bundle finishes. Ctrl+C (or SIGTERM) stops the server and the watchers.
📂 Sections Configuration
The sections-based API gives you full control over how components are organized in the sidebar.
Section Properties
| Property | Type | Description |
|---|---|---|
name |
string |
Display name for the section header |
type |
'elements' | 'pages' |
How items render (elements show demos, pages render directly) |
items |
Record<string, any> |
Element classes or page factories — a whole module namespace (import * as elements) is fine, see What the sidebar lists |
filter |
(name, item) => boolean |
Optional filter function to include/exclude items |
sort |
([a, itemA], [b, itemB]) => number |
Optional sort function for ordering items |
icon |
string |
Optional Material Symbols icon name |
collapsed |
boolean |
Start section collapsed (default: false) |
Advanced Example
import { setupWccTools } from '@design.estate/wcctools';
import * as allElements from './elements/index.js';
import * as pages from './pages/index.js';
setupWccTools({
sections: [
{
name: 'Pages',
type: 'pages',
items: pages,
},
{
name: 'Form Controls',
type: 'elements',
items: allElements,
icon: 'edit_note',
filter: (name) => name.startsWith('form-') || name.includes('input'),
sort: ([a], [b]) => a.localeCompare(b),
},
{
name: 'Layout',
type: 'elements',
items: allElements,
icon: 'dashboard',
filter: (name) => name.startsWith('layout-') || name.startsWith('grid-'),
},
{
name: 'Legacy',
type: 'elements',
items: allElements,
filter: (name) => name.startsWith('legacy-'),
collapsed: true, // Start collapsed
},
],
});
What the sidebar lists
Catalogue barrels usually export more than components: helpers, constants, styles, abstract base classes. wcctools classifies every entry of an elements section itself, before your filter and sort run, so no curated list is needed:
| Entry | Sidebar |
|---|---|
A class extending HTMLElement with a static demo (a template factory, or a non-empty array of them) |
Listed and navigable |
A registered custom element (customElements.getName(ctor) is set) without a usable demo |
Listed under Without demo |
| Anything else — functions, objects, strings, numbers, unregistered base classes | Not listed |
- Without demo is a collapsed, muted group at the bottom of each
elementssection. Its entries are not clickable (aria-disabled, no route); they keep demo coverage visible. A search opens the group while it has matches, and matches names, tag names and demo groups like any other entry. - A
pagessection lists only template factories (functions). - A route or pin that points at an entry that is not listed clears the preview and shows an explanation in the frame ("… has no demo", "… cannot be previewed", "… was not found") instead of a blank or stale preview.
Migration from setupWccTools(elements, pages)
The two-argument form is removed in 5.0.0. Pass the same maps as sections; a whole module namespace still works as items:
setupWccTools({
sections: [
{ name: 'Pages', type: 'pages', items: pages },
{ name: 'Elements', type: 'elements', items: elements },
],
});
Features
🎯 Live Property Editing
The properties panel finds the nearest matching demo instance through light DOM and open shadow roots, without imposing a nesting-depth limit. Composed examples can place their controls inside theme providers, panels, and layout wrappers.
The properties panel automatically detects and allows editing of:
| Property Type | Editor |
|---|---|
| String | Text input |
| Number | Number input |
| Boolean | Checkbox |
| Enum | Select dropdown |
| Object/Array | JSON editor modal |
WCC only creates editors for public reactive properties with supported runtime metadata. Bare @property() declarations use Lit's default string metadata; internal @state() fields and properties with unsupported or unavailable metadata are skipped because TypeScript types are not available at runtime. Use @property({ type: Object, attribute: false }) or @property({ type: Array, attribute: false }) when a complex property should be editable.
📱 Viewport Testing
Test your components across different screen sizes:
- Phone — 400px width
- Phablet — 600px width
- Tablet — 1024px width
- Desktop — Available preview width
The preview document runs in an iframe of the selected width, so both width @media queries and wccToolsViewport container queries respond to it.
On browser viewports up to 600px wide, the catalogue sidebar moves above the preview and the property controls wrap into a compact bottom toolbar. Selecting phone, phablet, or tablet still gives the preview frame its exact target width; wider targets scroll inside the frame instead of widening the document.
🌓 Theme Support
Components automatically adapt to light/dark themes. Use CSS custom properties with the theme manager:
import { cssManager } from '@design.estate/dees-element';
public static styles = [
css`
:host {
color: ${cssManager.bdTheme('#1a1a1a', '#e5e5e5')};
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
}
`
];
🎬 Screen Recording
Record component demos directly from the catalogue with full export control:
- Viewport Recording — Record just the component viewport
- Full Screen Recording — Capture the entire screen
- Audio Support — Add microphone commentary with live level monitoring
- Video Trimming — Trim start/end before export with a visual timeline
- 60fps Capture — Smooth, high-bitrate recording at up to 60 frames per second
- MP4 Export — Universal H.264/AAC format via mediabunny WebCodecs conversion (plays everywhere: WhatsApp, iMessage, Slack, etc.)
- WebM Export — Native VP9 output for maximum quality
Click the red record button in the bottom toolbar, choose your format (MP4 or WebM), and start recording.
🧪 Demo Tools
DeesDemoWrapper lives in @design.estate/dees-element/demotools; import it from there. The @design.estate/dees-wcctools/demotools re-export is removed in 5.0.0:
import '@design.estate/dees-element/demotools';
@customElement('my-component')
export class MyComponent extends DeesElement {
public static demo = () => html`
<dees-demowrapper .runAfterRender=${async (wrapper) => {
// Find elements using standard DOM APIs
const myComponent = wrapper.querySelector('my-component');
// Simulate user interactions
myComponent.value = 'Test value';
await myComponent.updateComplete;
// Work with multiple elements
wrapper.querySelectorAll('.item').forEach((el, i) => {
console.log(`Item ${i}:`, el.textContent);
});
}}>
<my-component></my-component>
<div class="item">Item 1</div>
<div class="item">Item 2</div>
</dees-demowrapper>
`;
}
🎭 Multiple Demos
Components can expose multiple demo variations:
@customElement('my-button')
export class MyButton extends DeesElement {
public static demo = [
() => html`<my-button variant="primary">Primary</my-button>`,
() => html`<my-button variant="secondary">Secondary</my-button>`,
() => html`<my-button variant="danger">Danger</my-button>`,
];
}
Each demo appears as a numbered item in an expandable folder in the sidebar.
🗂️ Demo Groups
Organize elements into groups within a section for better discoverability:
@customElement('my-input')
export class MyInput extends DeesElement {
// Single group
public static demoGroups = 'Form Controls';
// Or multiple groups — element appears in each
public static demoGroups = ['Form Controls', 'Inputs'];
public static demo = () => html`<my-input></my-input>`;
}
Groups appear as collapsible headers in the sidebar, sorted alphabetically. Searching matches group names too — searching "Form Controls" shows all elements in that group.
⏳ Async Demos
Return a Promise from demo for async setup:
public static demo = async () => {
const data = await fetchSomeData();
return html`<my-component .data=${data}></my-component>`;
};
🎯 Container Queries
Components can respond to their container size using the wccToolsViewport container, which wraps every rendered demo in the preview document:
public static styles = [
css`
@container wccToolsViewport (min-width: 768px) {
:host {
flex-direction: row;
}
}
@container wccToolsViewport (max-width: 767px) {
:host {
flex-direction: column;
}
}
`
];
Component Guidelines
Required for Catalogue Display
- Components must expose a static
demoproperty returning a Lit template (or a non-empty array of template factories); registered elements without one appear under Without demo - Use
@property()or@property({ type: ... })decorators with theaccessorkeyword for editable properties - Export component classes for proper detection
Best Practices
@customElement('best-practice-component')
export class BestPracticeComponent extends DeesElement {
// ✅ Static demo property (single or array)
public static demo = () => html`
<best-practice-component
.complexProp=${{ key: 'value' }}
simpleAttribute="test"
></best-practice-component>
`;
// ✅ Typed properties with defaults (TC39 decorators)
@property({ type: String })
accessor title: string = 'Default Title';
// ✅ Complex property without attribute
@property({ type: Object, attribute: false })
accessor complexProp: { key: string } = { key: 'default' };
// ✅ String editor with a compile-time union
@property({ type: String })
accessor variant: 'small' | 'medium' | 'large' = 'medium';
}
Keyboard Use
The sidebar is an ARIA tree with a single tab stop (the selected entry, or the last focused one):
| Key | Action |
|---|---|
Tab / Shift+Tab |
Move between the search field, the tree, the resize handle and the toolbar |
↓ in the search field |
Enter the tree |
Esc in the search field |
Clear the search |
↑ / ↓ |
Previous / next visible entry |
Home / End |
First / last visible entry |
→ |
Expand a section, demo folder or Without demo; on an expanded one, move to its first child |
← |
Collapse; on a collapsed or leaf entry, move to its parent |
Enter / Space |
Open the entry (or toggle a header) |
ContextMenu / Shift+F10 |
Open the entry's context menu (pin, show in group) at the entry; ↑ / ↓ / Home / End move, Enter / Space act, Esc / Tab close — focus returns to the entry |
The sidebar resize handle is a focusable separator: ← / → resize by 10 px (Shift for 50 px), Home / End jump to the minimum / maximum width. Theme and viewport controls are toggle buttons (aria-pressed), and Esc leaves the native viewport, also while the focus is inside the preview.
Rendered demos keep their state while you search, pin, resize the sidebar, or switch theme or viewport; only selecting another entry or demo renders a new one.
URL Routing
The shell uses URL routing for deep linking:
/wcctools-route/:sectionName/:itemName/:demoIndex/:viewport/:theme
Examples:
/wcctools-route/Elements/my-button/0/desktop/dark
/wcctools-route/Views/view-dashboard/0/tablet/bright
/wcctools-route/Pages/home/0/desktop/dark
The preview document alone renders one demo from its own route, for tools that capture a single element:
/wcctools-preview?section=Elements&item=my-button&demo=0&theme=dark
API Reference
wcctools dev [--port <port>] [--host <address>] [--allowed-host <name>…]
Bundles and watches the catalogue as the project's @git.zone/tswatch configuration describes (or tswatch's element preset) and serves on one port:
| Path | Serves |
|---|---|
/ |
Redirects to /wcctools/ |
/wcctools/, /wcctools-route/... |
The shell |
/wcctools/typedrequest |
The typed API: the server info (getDevServerInfo), the bundle status (getBundleStatus) and captures (captureScreenshot, captureInteraction) |
| every other path | The catalogue's serve directory (default dist_watch/), with live reload for the preview document |
--port overrides the configured port; 0 picks a free port. --host sets the interface address to listen on (default 127.0.0.1); --allowed-host (repeatable) adds a hostname or address the server answers.
The shell follows the bundle status: per bundle the state of its latest run (started, finished or failed), the duration of the latest completed run and, while its latest completed run failed, the bundler's error message. A failed bundle is shown over the preview until a run of it finishes, which also reloads the preview.
Security model
The dev server serves your source and drives a headless browser, so by default it answers only this machine (with --host, every machine that can reach the port; the checks below guard against web pages, not against other machines):
- It listens on
127.0.0.1unless--hostsays otherwise. - It answers only the hosts it serves:
localhost,127.0.0.1,::1, the--hostaddress (with--host 0.0.0.0or::, every address of the machine's interfaces) and each--allowed-host. Any otherHostis refused with403, which defeats DNS rebinding. - Typed requests and every other request but
GET,HEADandOPTIONS, and WebSocket connections, must come from the server's own origin (http://<allowed host>:<port>). A request without anOriginheader is admitted only from a loopback connection, as local tools send it. So other web pages you visit cannot drive the typed API, and since neither the shell nor the preview sends CORS headers, they cannot read the catalogue either. The same rule refuses the shell behind a TLS-terminating reverse proxy (see Known Limitations).
wcctools dev owns the process signals: on SIGINT or SIGTERM it stops the server and tswatch's bundling and watching once, within tswatch's shutdown deadline (the longest stopGracePeriod of the configured watcher commands plus a margin), and exits.
wcctools screenshot <item | section/item> --out <file>
Bundles the catalogue of the working directory, serves it on a free loopback port, screenshots one demo in a headless browser, writes the image and stops.
wcctools screenshot my-button --out button.png # desktop width, dark theme
wcctools screenshot Elements/MyButton --demo 1 --viewport phone --theme bright --out button-phone.png
wcctools screenshot Pages/Home --viewport tablet --out home.jpg # a page, in full
wcctools screenshot my-card --viewport 720 --framing viewport --height 600 --scale 2 --out card@2x.png
| Option | Meaning |
|---|---|
<item> |
An entry's name or its element's tag; section/item when the name is in more than one section |
--out |
The file to write; .png, .jpg or .jpeg sets the format |
--demo |
The demo, counted from 0 as in the shell URL (default 0) |
--viewport |
phone (400), phablet (600), tablet (1024), desktop (1600, the default) or a width from 200 to 3840 pixels; the widths are the dees-domtools breakpoints |
--height |
Height of the preview window, which also sizes 100vh (default 800) |
--theme |
dark (default) or bright |
--framing |
element: the rendered element, the default for element demos; viewport: the visible window; fullpage: the whole document, the default for pages (cut at 10000 pixels) |
--scale |
Device pixel ratio, 1 (default) or 2 |
--quality |
JPEG quality from 1 to 100 (default 80) |
The capture waits for the preview's own signals, never for a fixed time: the preview bridge, the render's completed element updates and the document's fonts. It fails (exit code 1) when the bundle failed or the demo renders nothing; an entry that does not exist, or invalid options, exit with 2 and list what exists.
Captures on the typed API
While wcctools dev runs, its typed API takes the same captures, in one shared headless browser that starts with the first capture, closes after a minute without one and always closes with the server. Two captures run at once and eight wait; more are refused as busy. Captures wait while a bundle builds and are refused while one failed. Every capture ends when its client goes away, and at its deadline, which counts from its request and includes waiting for a slot or a bundle: 30 seconds for a screenshot, 135 seconds for an interaction (20 steps of 5 seconds, 5 seconds of observation and 30 seconds of set-up). TypedRequest.fire() gives up after 60 seconds unless given a timeoutMs, so fire interactions with a timeoutMs above their deadline, as below.
import { TypedRequest } from '@api.global/typedrequest';
import type { IReq_CaptureScreenshot, IReq_CaptureInteraction } from '@design.estate/wcctools/interfaces';
const endpoint = 'http://localhost:3002/wcctools/typedrequest';
const shot = await new TypedRequest<IReq_CaptureScreenshot>(endpoint, 'captureScreenshot').fire({
subject: { itemName: 'my-button' },
viewport: 'phone',
theme: 'dark',
});
// shot.image: { mimeType: 'image/png', width, height, dataBase64 }
const observed = await new TypedRequest<IReq_CaptureInteraction>(endpoint, 'captureInteraction').fire({
subject: { sectionName: 'Elements', itemName: 'MyInput' },
viewport: 'phone',
steps: [
{ action: 'click', selector: 'input' },
{ action: 'fill', selector: 'input', text: 'hello' },
{ action: 'press', key: 'Enter' },
{ action: 'waitFor', selector: '.error-message', state: 'hidden' },
],
observeMs: 500,
maxFrames: 12,
finalScreenshot: { framing: 'element' },
}, { timeoutMs: 140_000 }); // the interaction deadline is 135 s; fire() alone gives up after 60 s
// observed.frames: viewport JPEGs with timestampMs; observed.steps: per step ok/error and its time
An interaction renders the demo, records the page's screencast while it runs the steps, keeps watching for observeMs after the last one (at most 5000) and returns at most maxFrames frames (at most 40) spread evenly over that time, the first and the last always among them. Steps are click, hover, fill, press (a key such as Enter or Tab, on a selector or the focused element), waitFor (visible or hidden) and wait (at most 3000 ms); there are at most 20. A selector is a CSS selector matched in the preview document and in every open shadow root below it, so input finds the input inside a component; the first match in document order is used once it is visible and enabled. Each step may take 5 seconds; a failing step ends the script and is reported with its error (completed: false), the frames up to then included. A capture stays on the preview route: a navigation that would replace the preview document (a link, a form submission, a script assigning location, a reload, also to the same URL, to about:blank or to a blob: URL) is cancelled as it starts, and the step that started it fails with navigation to <url> blocked and ends the script, so the final screenshot still shows the preview. A navigation started after the last step fails that step. The preview's history begins with the preview, so going back leaves nothing. A screenshot, or an interaction without steps, fails when its demo navigates away. Same-document navigations, such as fragment links and history.pushState, pass.
Every wcctools command exits with code 2 on a usage error (an unknown command or option, a missing or invalid option value) and with 1 on any other failure.
wcctools check and wcctools fix
Check a catalogue against the wcctools component standard (docs/standard.md, a draft: rule ids, severities and defaults may still change until it is declared stable): file layout, naming, element registration, barrels, demos and the attribute surface of public properties, plus the theme, wording and component-index ratchets of the dees-catalog family. Both commands run in the repository root and read .smartconfig.json:
{
"@design.estate/wcctools": {
"standard": {
"profile": "base",
"tagPrefix": "my"
}
}
}
profile is base or dees; tagPrefix is the tag prefix without its hyphen. Exceptions go into standard.allowlist (rule id → '<file>#<member>' → '<category>: <reason>'), accepted error counts of an existing codebase into standard.baselines (rule id → file → count, only shrinking).
wcctools check # findings as text; exit 0 ok, 1 failing errors, 2 configuration or usage error
wcctools check --json # one IStandardReport on stdout
wcctools check --verbose # also info findings, covered findings and baseline slack
wcctools check --init-baseline # record the current error counts (only while no baselines exist)
wcctools check --update-baseline # lower the baselines to the current counts; refuses to raise any
wcctools fix --dry-run # show the moves and edits without writing
wcctools fix # apply every automatic fix
wcctools fix --rule props/reflect-primitive # also opt-in rules, one rule at a time
wcctools fix needs a clean work tree and never commits; it validates every path it would write before writing anything and refuses (exit code 2) a plan that leaves the repository root. It edits source text by syntax-node spans, so formatting and comments stay as they are; it moves files with git mv and rewrites the relative imports that pointed at them across the repository, and it checks again at the end. Review the result with git diff and commit it yourself.
setupWccTools(config)
Sets up the catalogue's preview document with sections configuration. The shell reads a data-only description of the sections from it; element classes and template factories never leave the preview document.
interface IWccSection {
name: string;
type: 'elements' | 'pages';
items: Record<string, any>;
filter?: (name: string, item: any) => boolean;
sort?: (a: [string, any], b: [string, any]) => number;
icon?: string;
collapsed?: boolean;
}
interface IWccConfig {
sections: IWccSection[];
}
setupWccTools(config: IWccConfig): void;
DeesDemoWrapper
Component for wrapping demos with post-render logic, imported from @design.estate/dees-element/demotools.
| Property | Type | Description |
|---|---|---|
runAfterRender |
(wrapper) => void | Promise<void> |
Callback after wrapped elements render |
The wrapper provides full DOM API access:
wrapper.querySelector()— Find single elementwrapper.querySelectorAll()— Find multiple elementswrapper.children— Access child elements directly
@design.estate/wcctools/interfaces
The contracts between the shell, the preview document and the dev server, for tools that drive a preview: the catalogue manifest (IWccCatalogManifest), the preview bridge the preview document installs as window.wccPreview (IWccPreviewBridge: render a demo, switch the theme, read and edit the rendered element's properties) and the dev server's typed requests including the capture requests and their results (IReq_CaptureScreenshot, IReq_CaptureInteraction, IWccImage, TWccInteractionStep, …), and the component standard's configuration and reports (IStandardConfig, IStandardReport, IStandardFixResult).
import type { IWccPreviewBridge } from '@design.estate/wcctools/interfaces';
const bridge: IWccPreviewBridge = previewFrame.contentWindow.wccPreview;
await bridge.render({ sectionName: 'Elements', itemName: 'MyButton', demoIndex: 0 });
const properties = await bridge.getProperties();
Known Limitations
- Captures need Chrome or Chromium on the
PATH. In CI (CIset) or as root, the browser starts without its sandbox and prints a warning banner saying so. - Each start of the capture browser prints its launch arguments and executable on standard output (from
@push.rocks/smartbrowser, which has no quiet option yet). The lines appear in thewcctools devlog on the first capture and in the output ofwcctools screenshot, which therefore has no machine-readable output mode and writes images only to files. - A selector is matched within each document or shadow root on its own:
my-input inputdoes not reach intomy-input's shadow root,inputdoes. - Captures wait for element updates and fonts, not for images or other resources a demo loads.
- Captures cancel navigations through the Navigation API's
navigateevent before the demo's own listeners see it, so a demo that routes by callingintercept()on cross-document navigations does not route in a capture (itsintercept()call throws). Ajavascript:URL navigates without that event; one that evaluates to a string replaces the preview document. A navigation Chrome does not let the page cancel (some history traversals; the preview's history holds only the preview, so there is none to traverse) is aborted at the network instead, may be reported on the step after the one that started it, and is not stopped when it loads nothing from the network. - The shell does not work through a TLS-terminating reverse proxy, even for a name given with
--allowed-host: its typed requests and WebSocket connections then carry anhttps:origin (or the proxy's port), and the dev server admits onlyhttp://<allowed host>:<port>with its own port, so it refuses them; plain page loads still pass. Reach the dev server over plain HTTP on its own port, for example through an SSH tunnel.
Project Structure
my-component-library/
├── src/
│ ├── elements/ # UI components
│ │ ├── my-button.ts
│ │ ├── my-card.ts
│ │ └── index.ts
│ ├── views/ # Full-page layouts
│ │ ├── view-dashboard.ts
│ │ └── index.ts
│ ├── pages/ # Documentation pages
│ │ ├── home.ts
│ │ └── index.ts
├── html/
│ ├── index.ts # setupWccTools: the catalogue
│ └── index.html # the preview document
└── package.json # "watch": "wcctools dev"
Browser Support
- ✅ Chrome/Edge (latest)
- ✅ Firefox (latest)
- ✅ Safari (latest)
- ✅ Mobile browsers with Web Components support
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.