# Tool modules

> Plainkit 0.28.0. Generated by scripts/build-skills.mjs from the header comments of dist/modules/<tool>/<tool>.js. Do not edit: change the source and run the generator.

The tools are the modules unit, separate from the runtime: each is a module in `dist/modules/<tool>/<tool>.js` (its own zip, `plainkit-modules-<version>.zip`, and its own manifest, `dist/modules/manifest.json`; the runtime `dist/` holds no tool). Keep the unit at `dist/modules/` next to the runtime, or host it apart and name the runtime once (`<meta name="plainkit-runtime" content="https://host/plainkit/">` plus an import map for the `../../js/*.js` imports; see core/README.md). Each tool has one entry point, `mountX(container, options)`, that returns a handle (with `destroy()` at least). The module adds `plainkit.css` to the document if it is missing and loads the elements it needs, so a page needs no other setup. The text under each heading is the module's own documentation, verbatim.

| Tool | Import | Entry point |
|---|---|---|
| code-explorer | `dist/modules/code-explorer/code-explorer.js` | `mountCodeExplorer` |
| scorecard | `dist/modules/scorecard/scorecard.js` | `mountScorecard` |
| performance | `dist/modules/performance/performance.js` | `mountPerformance` |
| console | `dist/modules/console/console.js` | `mountConsole` |
| logs | `dist/modules/logs/logs.js` | `mountLogs` |
| log-settings | `dist/modules/log-settings/log-settings.js` | `mountLogSettings` |
| devtools | `dist/modules/devtools/devtools.js` | `mountDevTools` |
| tool-dock | `dist/modules/tool-dock/tool-dock.js` | `mountToolDock` |
| quality | `dist/modules/quality/quality.js` | `mountQuality` |
| layout-builder | `dist/modules/layout-builder/layout-builder.js` | `mountLayoutBuilder` |
| theme-editor | `dist/modules/theme-editor/theme-editor.js` | `mountThemeEditor` |
| gallery | `dist/modules/gallery/gallery.js` | `mountGallery` |

## code-explorer

```text
The code explorer as a module: mountCodeExplorer(container, options) puts a file tree, tabs, a code viewer, search and an outline in
`container`, fed by a snapshot or a ready provider (modules/code-explorer/providers.js: SnapshotProvider, ApiProvider, LazyProvider,
FeedProvider; tools/snapshot.mjs produces a snapshot document or, for LazyProvider, a lean file list with no content).

  const explorer = await mountCodeExplorer(el, { snapshot: 'snapshot.json', file: 'src/app.js', search: 'TODO', theme: 'light', height: '32rem' });

  // Or a lean file list plus same-origin content, fetched lazily (issue 196: the SDK's own site uses this by default, since
  // a snapshot embeds every file's full text up front). raw is the base URL real files sit under, resolved however the host likes:
  const provider = await LazyProvider.connect('index.json', rawBaseUrl);
  const explorer = await mountCodeExplorer(el, { provider, file: 'src/app.js' });

Options: snapshot (a URL of a snapshot JSON, or the parsed object), provider (a ready provider instead of a snapshot), file (open
this path, `line` picks the row), search (run this query), theme ('dark' | 'light'), height (any CSS length, or 'fill'; default 32rem),
patterns (host-defined [{ name, pattern, label? }], see reports.js -- adds a "Patterns" choice to the Reports tab, which
always offers largest files, longest methods and duplicate blocks even with no patterns set).
Returns { element, openFile(path, { line }), search(query), destroy() }. The document must be able to load the SDK stylesheets; any
that are missing are added (the explorer's own stylesheet is added by the element).
```

## scorecard

The full text is long (3598 characters): it is in `references/full/tools.md`, section `scorecard` (kept apart so the group files stay short).

## performance

```text
The performance monitor as a module: mountPerformance(container, options) shows how the page is doing right now: the Core Web Vitals
(LCP, CLS, INP, FCP, TTFB), a live frame rate, main-thread long tasks, DOM size, JS heap and what the page loaded. It observes the
browser's own performance APIs, adds no dependency and makes no network request. Built only from SDK components (pk-stat, pk-table,
pk-button, pk-card, pk-cluster); nothing here draws its own widgets.

  const perf = await mountPerformance(el, { interval: 1000, history: 60, theme: 'dark', height: '32rem' });
  perf.snapshot();   // the numbers as an object

Options: target (the document to watch; default the container's), interval (ms between updates, default 1000), history (samples kept
for the sparklines, default 60), autostart (default true), theme ('dark' | 'light'), height (any CSS length, or 'fill'), onsample (called with each snapshot).
Returns { start(), stop(), running(), snapshot(), destroy() }. Pure maths lives in js/perf-logic.js. Metrics a browser does not
expose (INP and heap outside Chromium, for one) show a dash rather than a guess.
```

## console

```text
The dev console as a module: mountConsole(container, options) is a live view of what the page, the SDK and the app are doing:
everything sent to console.*, uncaught errors and rejected promises, every pk-* event the SDK's elements fire, the network requests
the page made, which pk-* elements are on the page (and whether each is registered), and the environment (theme, density, viewport,
preferences, Blazor). Built only from SDK components (pk-tabs, pk-table, pk-input, pk-button, pk-button-group, pk-cluster); it draws nothing itself.

  const dev = await mountConsole(el, { tab: 'console', height: '28rem', theme: 'dark' });
  dev.log('warn', 'Something to look at');

Options: capture (which feeds to record; any of 'console', 'errors', 'events', 'network'; default all), max (entries kept, default
500), events (pk-* event names to record; default: every event in elements/api.json, else a built-in list), tab (first tab shown),
theme, height (any CSS length, or 'fill'). Returns { log(level, text), clear(), entries(), select(tab), destroy() }.
There is deliberately no JavaScript prompt: running typed code needs eval, which the SDK's strict CSP (and its security scan) forbids.
destroy() puts console.* back exactly as it was found.
```

## logs

```text
The logs viewer as a module: mountLogs(container, options) is a live view of what the SDK and the app logged through js/log.js
(createLogger('checkout').warn(...)): the entries already in the ring buffer when it mounts, then every new one as it happens, whatever
the console level (an entry below it is in the buffer only and is marked "buffered only"). Filter by minimum level, scope and text; a
row shows its detail (objects as JSON, an Error with its stack); copy, export and import as JSON; pause the view; clear. Built only
from SDK components (pk-table, pk-input, pk-select, pk-button, pk-button-group, pk-badge, pk-card, pk-code-block, pk-empty-state,
pk-cluster, pk-stack); it draws nothing itself.

  const logs = await mountLogs(el, { level: 'info', height: '28rem' });
  logs.pause(); logs.resume(); logs.select(3); logs.entries();

Options: level (the minimum level shown first: debug, info, warn or error; default debug), scopes (scopes selected first), max (entries
kept, default 1000), order ('newest' first, the default, or 'oldest'), theme, height (any CSS length, or 'fill').
Returns { pause(), resume(), isPaused(), clear(), entries(), select(id), filter({ level, scopes, text }), destroy() }. pause() freezes what
is drawn (entries keep arriving and show on resume()). clear() empties the view and the SDK's ring buffer. Pure logic is js/log-view-logic.js.
```

## log-settings

```text
The logging settings as a module: mountLogSettings(container, options) edits how js/log.js behaves: the global level, a level for any
scope (the scopes that have logged so far are listed; add one by name), and which outputs each level is sent to (console, toast,
alert and any output registered with registerLogOutput). "Send a test" logs one entry per level under the scope settings-test, using
the settings as edited (for this page only); Save keeps them in localStorage (configureLogging(..., { persist: true })); Reset goes
back to the defaults (resetLogging()). ?pk-log= in the address and <html data-pk-log> still decide the level when the page loads.
Built only from SDK components (pk-select, pk-input, pk-field, pk-table, pk-checkbox, pk-button, pk-badge, pk-alert, pk-stack,
pk-cluster); it draws nothing itself. Every value is checked with normalizeConfig before it is applied.

  const settings = await mountLogSettings(el, { onsave: config => console.log(config) });

Options: theme, height (any CSS length, or 'fill'), onsave(config), onchange(draft). Returns { config(), refresh(), save(), reset(), test(), destroy() }.
Pure logic is js/log-settings-logic.js.
```

## devtools

```text
The dev tools as a module: mountDevTools(container, options) puts the SDK's live tools (console, logs, logging settings, performance, and any panel you add)
in one tabbed surface, either docked over the page (Ctrl+` toggles it, like a browser's dev tools) or inline in a container.
Built only from SDK components (pk-tabs, pk-button, pk-button-group) around the console, logs, log settings and performance modules, plus the Quality
(the SDK page checks on the live page), Inspector (pk-* elements on the page), Theme (the theme editor, live on this page) and Layout builder
(a scratch instance of the layout builder module) panels in panels.js. The floating/docking/resize/hotkey shell is the generic
tool-dock module (../tool-dock/tool-dock.js); mountDevTools is a thin consumer of it, its own BUILT_IN panels passed in as tool-dock's `panels`.

  const tools = await mountDevTools(null, { mode: 'dock' });      // a floating button and a bottom dock on any page
  const page  = await mountDevTools(el, { mode: 'inline' });      // the same tabs, filling a container
  tools.toggle(); tools.select('console');

Options: mode ('dock' | 'inline'; default 'dock'), hotkey (dock only; default 'Ctrl+`', '' turns it off), tab (first tab), open (dock
only; start open), size ('small' | 'medium' | 'large'; dock height, default 'medium'), theme, panels (extra tabs, below).
A panel is { id, title, mount(element, context) } where mount returns nothing or { destroy(), activate(), deactivate() }; activate and
deactivate are called as its tab is shown and hidden, so a panel can pause work while unseen. context is { doc, win, theme, whileHidden(fn), isTool(el) }.
Returns { open(), close(), toggle(), isOpen(), select(id), tabs(), destroy() }.
```

## tool-dock

```text
A tabbed panel that floats/docks over the whole page (or fills a container): dock to the bottom edge, resize between named
sizes, toggle with a hotkey, pin open or closed. This is the generic shell mountDevTools uses for its own tabs (console, logs,
quality...); an app with its own tool-type panels (properties, history, an outline, a console, a chat) around a canvas/record/page
points mountToolDock at its own panels instead. Not pk-dock: pk-dock is a fixed workspace of resizable splits and tab groups for
arranging several panels of page content against each other; mountToolDock is one panel with a tab strip that floats over the page.

  const tools = await mountToolDock(null, { mode: 'dock', panels, label: 'My tools', launcherLabel: 'Tools' });
  const page  = await mountToolDock(el, { mode: 'inline', panels, label: 'My tools', launcherLabel: 'Tools' });
  tools.toggle(); tools.select(panels[0].id);

Options: mode ('dock' | 'inline'; default 'dock'), hotkey (dock only; default 'Ctrl+`', '' turns it off), tab (first tab), open (dock
only; start open), size ('small' | 'medium' | 'large'; dock height, default 'medium'), theme, panels (required; see below),
label (required; the surface's accessible name), launcherLabel (required; the floating launcher button's text).
A panel is { id, title, mount(element, context) } where mount returns nothing or { destroy(), activate(), deactivate() }; activate and
deactivate are called as its tab is shown and hidden, so a panel can pause work while unseen. context is { doc, win, theme, whileHidden(fn), isTool(el) }.
Returns { open(), close(), toggle(), isOpen(), select(id), tabs(), destroy() }.
```

## quality

```text
The quality checks as a module: mountQuality(container, options) runs the SDK's own page checks (js/quality.js: accessibility, layout,
spacing, touch targets on a phone, nested scrollers, literal colours, focus indicators) on a live page and shows a score and every
finding. It is standalone (put it anywhere) and it is the Quality tab of the dev tools. Built only from SDK components (pk-stat,
pk-table, pk-button); it draws nothing itself.

  const q = await mountQuality(el, { autorun: true });          // checks the page the container is in
  const r = await q.run();                                      // { score, findings, width }

Options: root (the element or document to check; default the container's document body), around (a function that receives a callback
and runs it with anything that must not be measured hidden; default hides the module itself, and the dev tools pass their own so the
dock is left out too), phone (force the phone checks on or off; default on when the window is 640px wide or less), autorun, theme,
height (any CSS length), onresult (called with each result). Returns { run(), results(), destroy() }. Scoring and rows are in
js/inspect-logic.js. This is a different thing from the Scorecard: the scorecard scores many targets and the framework; this checks the
page you are on, right now.
```

## layout-builder

The full text is long (5330 characters): it is in `references/full/tools.md`, section `layout-builder` (kept apart so the group files stay short).

## theme-editor

The full text is long (4420 characters): it is in `references/full/tools.md`, section `theme-editor` (kept apart so the group files stay short).

## gallery

```text
The SDK gallery as a page of its own (the embed page, <pk-gallery>, the frames of the docs): ONE side nav (a pk-side-nav) whose tree is exactly the content: section > group > item. Every nav
entry is a view with its own URL hash (#/elements/pk-button), so each view shows one topic and back/forward work. Samples render in iframes (frame.js) so the phone switch is real.
The site app has its own Gallery module over the same parts (chrome.js, runtime.js, pages.js). Framework-free; ES module driven by gallery.data.js.

  mountGallery(container, { kind, group, control, theme, width, filter, chrome, sections })   // resolves once the first view is drawn
  setGallerySections(sections)                                                             // replace the sections a host adds to the Details drawer

sections: what a host adds to the docked inspector (Details), as plain serialisable data so it also works when the gallery runs in a frame:
[{ tag?, title, open?, lines?, columns?, rows?, code? }], all text (js/gallery-sections.js has the shape and the limits). A framed gallery
(embed.html) takes them by message from the embedding page: <pk-gallery sections='[...]'> does that, or post
{ type: 'pk-gallery-sections', sections } to the frame after it posts { type: 'pk-gallery-ready' } to its parent.
```

## element inspector

File `dist/js/element-inspector.js`, entry point `createElementInspector(container)`.

```text
The element inspector: shows what an element is, from its API metadata, and what it looks like right now. It draws the element's tag and
summary, its live markup (with a copy button), then the properties, slots, events, CSS parts and properties, methods and accessibility
notes: the single place the gallery's element pages show their API reference. The gallery's docked Details drawer uses it; a layout
builder can use it for the selected element. A host adds its own sections with extraSections.
Built only from SDK components (pk-stack, pk-accordion, pk-accordion-item, pk-code-block, pk-table, pk-empty-state); it makes no markup
strings and loads nothing: the host has already started the elements (initPlainkit) and passes the API data.

  const inspector = createElementInspector(container);
  inspector.show({ meta, element, extraSections });   // meta: the element's API entry; element: the live element (optional)
  inspector.refresh();                                // the live element changed: redraw the markup and the extra sections
  inspector.setElement(el);                           // the live element was replaced by another of the same tag: follow it (markup and extra sections only)
  inspector.show(null);                               // nothing selected: an empty state that says what the inspector shows
  inspector.destroy();

show({ meta, element, markup, extraSections }): element is the live element (its markup follows it); markup replaces the live markup with
fixed text when there is no element. extraSections is an array of { title, render(container, { meta, element }), open? }: each becomes a
pk-accordion-item after the built-in ones, and render fills its container with SDK components (it runs on show() and on every refresh()).
A render that throws is logged (scope element-inspector) and its section shows a short note; the others still draw.
Pure logic is js/element-inspector-logic.js.

sectionFromData(data) turns a plain, serialisable descriptor ({ title, open?, lines?, columns?, rows?, code? }, all text: see js/gallery-sections.js)
into such a section, drawn with the same components (p, pk-table, pk-code-block) and only ever as text: it is how a host in another frame adds one.
```

## The code explorer element

The code explorer is also a custom element, `<pk-code-explorer>`, defined by `dist/modules/code-explorer/element.js` (events `pk-code-explorer-open` and `pk-code-explorer-error`, height hook `--pk-code-explorer-height`). `mountCodeExplorer` creates it for you.

## Dev tools workspace

`mountDevToolsWorkspace(container, options)` (`./plainkit/modules/devtools/workspace.js`, same unit as `mountDevTools`) is the toolkit's own dev tools page in one call: a `pk-tabs` strip (Gallery, Files, Scorecard) over the chosen workspace, plus the dev tools dock. The workspaces are the `<pk-gallery>` element, `mountCodeExplorer` and `mountScorecard`; each loads on first use, so opening on one loads only that one. Data comes in as options: `gallery` (`{ sections, src, chrome, height }`; `sections` are the Details drawer sections of `pk-gallery`), `files` (`{ snapshot | provider | load, height, file, line, count }`), `scorecard` (`{ targets, checks, historyKey, autorun, height }`), and `workspace` (the first tab), `workspaces` (which tabs), `tool` (the dock tab), `dock` (false to leave the dock out), `open`, `panels`, `theme`, `onworkspace(key)`. A workspace with no data shows an empty state. It returns `{ ready, select(key), workspace(), update(patch), dock, destroy() }`; `update({ files: { snapshot } })` hands data over after the mount.

## Tool dock and tray

`mountDevTools` is itself a thin consumer of `mountToolDock` (`./plainkit/modules/tool-dock/tool-dock.js`), the generic floating/docked tabbed panel underneath it — a single tool panel that docks to the bottom of the page or fills a container, toggled by a hotkey, resizable between named sizes, given its own `panels` (`{ id, title, mount(el, context) }`, same shape as `mountDevTools`'s). Point it at your own app content (`mountToolDock(null, { mode: 'dock', panels, label: 'My tools', launcherLabel: 'Tools' })`) when you want that pattern — a canvas/record/page as the main surface with tool-type panels (properties, history, an outline, a console, a chat) docked around it — for something other than the dev tools. It is not `pk-dock`: `pk-dock` is the multi-pane workspace element (Palette | Canvas | Properties, drag-to-dock, layout builder's own chrome above), while `mountToolDock` is one floating tabbed panel, not a workspace of panes. In your own markup the same job is the `pk-tray` element: `<pk-tray label="Tools" launcher-label="Open tools" hotkey="Ctrl+`" sizes>...</pk-tray>` pins a non-modal panel to a viewport edge (`edge`: bottom, top, start, end; `size`: small, medium, large) with a floating launcher and no focus trap, closes on Escape inside it, Close or the launcher, and reports `pk-open`, `pk-close` (cancelable) and `pk-size-change` (Blazor: `PkTray`, `@bind-Open`, `@bind-Size`); use `pk-drawer` instead when the panel should be modal.

For the canvas of a page editor,`pk-design-surface` frames the page being edited: `<pk-design-surface label="Page canvas" width="tablet">...</pk-design-surface>` is a scrolling ground with full, tablet or phone width frames and an inert page (`interactive` lifts that). You set the properties `selected`, `dropTarget` and `chipFor` to page nodes, flag nodes `data-surface-hidden` or `data-surface-empty`, and the surface draws the marks as overlay boxes and places the action chip (slot `chip`, real buttons you own) beside `chipFor`; `dragging` hides the chip during an external drag. It reports `pk-surface-pick`, `pk-surface-hover` and `pk-surface-key` and never changes the selection itself; `reveal(node)` scrolls a node into view (Blazor: `PkDesignSurface`, with `Chip` and `Empty` fragments; the node properties are set by script).

For a design tool's canvas, `pk-canvas` is the pan and zoom ground: `<pk-canvas label="Artboard" grid="20">...</pk-canvas>` lays its content out in a world you drag, scroll or pinch around and zoom (Ctrl and the wheel, the plus and minus buttons, `+`, `-`, `0`) over an optional grid, converts between screen and world units with `pointToWorld` (snapped to the grid) and `worldToPoint`, frames any descendant you flag `data-surface-selected`, and reports `pk-view-change`, `pk-canvas-pick` and `pk-canvas-key` while the page keeps ownership of the selection (Blazor: `PkCanvas`, `@bind-Zoom`).

## Mounting the layout builder

Pages built by your users from the SDK's own elements, kept as JSON and exported as CSP-safe markup; the host stores the page, the builder stores nothing.

```js
import { mountLayoutBuilder } from './plainkit/modules/layout-builder/layout-builder.js';

const builder = await mountLayoutBuilder(document.getElementById('editor'), {
    html: startingMarkup,   // or model: a saved document
    onchange: ({ model, reason }) => keepDraft(model),
    onsave: ({ model, html }) => savePage(model, html),
});
```
