# Skin format

A skin is the `skin/` folder of its project ([MCP server](/docs/mcp/#project-layout-and-what-writes-do)).
The extension applies it once the project has a valid `skin/skin.json`.

```text
skin/
  skin.json      metadata, and which files make up the skin (required)
  trigger.js     decides where the skin is on (required)
  style.css      optional
  content.html   optional
  behavior.js    optional
```

The file names are yours to choose, except `skin.json`: it names the others.

## skin.json

```ts
interface SkinJson {
  name: string;        // the skin's title in the side panel; at most 40 characters
  description: string; // what it does; at most 160 characters
  activeWhen: string;  // where it is on, in plain words, for people; at most 80 characters
  trigger: string;     // file in skin/ with the trigger, e.g. "trigger.js"
  css?: string;        // file in skin/ with CSS
  html?: string;       // file in skin/ with HTML
  js?: string;         // file in skin/ with JavaScript
}
```

- The text fields are single-line and non-empty. Unknown keys are rejected.
- File fields are paths inside `skin/`: letters, digits, `.`, `_`, `-` and `/`, no `..`.

## trigger.js

The body of an async function, run in the page on each page load and in-page navigation. The skin is
on only while it returns `true`.

```ts
// What the body can use:
declare const url: string;          // location.href when the trigger started
declare const document: Document;   // the page's document
declare const location: Location;   // the page's location
// It returns: true to apply the skin. Anything else means no.
```

- Only the literal `true` counts. Any other value, an error, or taking longer than **3 seconds**
  means no.
- It may `await`, for example for an element to appear.
- It runs on every web page you open, for every skin that's on: check cheap things first, like the
  hostname, and return early.

## How the parts are applied

When the trigger returns `true`, once per page view:

| Part | Applied as |
|---|---|
| `css` | A stylesheet added to the page |
| `html` | Inserted into a container at the end of the page's `<body>` |
| `js` | Run once, after the CSS and HTML are in |

- The JavaScript sees the page's DOM, not the page's own JavaScript variables. It can't `import`
  anything, and the page's Content Security Policy doesn't stop it.
- Prefer CSS. Use JavaScript only for what CSS can't do.
- If your JavaScript watches the page with a `MutationObserver`, disconnect it while you change the
  page and reconnect after, and only write what actually changed. Otherwise each change triggers
  the next and the page never settles.
- When the trigger stops returning `true`, the CSS and HTML are removed. Whatever the JavaScript did
  stays until the page reloads.

## Example: hello world

A skin that adds a greeting to `example.com`.

`skin/skin.json`

```json
{
  "name": "Hello world",
  "description": "Adds a greeting banner to example.com",
  "activeWhen": "On example.com",
  "trigger": "trigger.js",
  "css": "style.css",
  "html": "content.html",
  "js": "behavior.js"
}
```

`skin/trigger.js`

```js
return location.hostname === "example.com";
```

`skin/content.html`

```html
<div class="hello-skin">Hello from your first skin! <span class="hello-time"></span></div>
```

`skin/style.css`

```css
.hello-skin {
  position: fixed;
  right: 16px;
  bottom: 16px;
  padding: 10px 14px;
  border-radius: 8px;
  background: #1d1d1f;
  color: #fff;
  font: 14px/1.4 system-ui, sans-serif;
}
```

`skin/behavior.js`

```js
const time = document.querySelector(".hello-skin .hello-time");
if (time) time.textContent = `It's ${new Date().toLocaleTimeString()}.`;
```

Write these files to a project with `writeGadgetFile` ([MCP server](/docs/mcp/)), publish the
draft, and open `https://example.com`.