jkunz 2fd698e1fd
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / metadata (push) Skipped
v7.0.0
2026-10-06 17:56:30 +00:00
2026-01-04 11:29:19 +00:00
2026-10-06 17:56:30 +00:00
2026-10-06 17:56:30 +00:00
2026-10-06 17:56:30 +00:00
2026-10-06 17:56:30 +00:00

@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 screenshot and 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.

  1. Keep @design.estate/wcctools as a devDependency at ^6.0.0.
  2. Replace the watch script: "watch": "wcctools dev" (it reads the same @git.zone/tswatch configuration from .smartconfig.json, or uses tswatch's element preset).
  3. Open the address wcctools dev prints (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:

  1. wcctools dev listens on 127.0.0.1 only. 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: the Host and Origin checks below keep web pages in your browser from using the server, they do not authenticate other machines.
  2. Requests for a Host the server does not serve are refused with 403 and a message naming the --allowed-host to add.
  3. Typed requests and other state-changing requests, and WebSocket connections, are admitted only from the server's own origin; a request without an Origin header only from this machine.
  4. 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 elements section. 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 pages section 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

  1. Components must expose a static demo property returning a Lit template (or a non-empty array of template factories); registered elements without one appear under Without demo
  2. Use @property() or @property({ type: ... }) decorators with the accessor keyword for editable properties
  3. 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.1 unless --host says otherwise.
  • It answers only the hosts it serves: localhost, 127.0.0.1, ::1, the --host address (with --host 0.0.0.0 or ::, every address of the machine's interfaces) and each --allowed-host. Any other Host is refused with 403, which defeats DNS rebinding.
  • Typed requests and every other request but GET, HEAD and OPTIONS, and WebSocket connections, must come from the server's own origin (http://<allowed host>:<port>). A request without an Origin header 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 element
  • wrapper.querySelectorAll() — Find multiple elements
  • wrapper.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 (CI set) 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 the wcctools dev log on the first capture and in the output of wcctools 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 input does not reach into my-input's shadow root, input does.
  • Captures wait for element updates and fonts, not for images or other resources a demo loads.
  • Captures cancel navigations through the Navigation API's navigate event before the demo's own listeners see it, so a demo that routes by calling intercept() on cross-document navigations does not route in a capture (its intercept() call throws). A javascript: 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 an https: origin (or the proxy's port), and the dev server admits only http://<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

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.

S
Description
build web component catalogs with viewport preview, property manipulation and precomposed views
Readme
3.9 MiB
Languages
TypeScript 99.7%
HTML 0.2%