···11+---
22+name: diffuse-facet
33+description: Create an interface or feature facet for Diffuse
44+user-invocable: true
55+version: 0.1.0
66+---
77+88+Create a Diffuse facet and produce the HTML ready to paste into the `code/` page.
99+1010+## Step 1 — Read the docs
1111+1212+Use the read tool to read these files:
1313+1414+- `docs/architecture.txt` — system overview, facet rules, foundation API
1515+- `docs/elements.txt` — all available custom elements with code examples
1616+- `example/index.html` — a representative interface facet to use as a reference
1717+- Any specific definition you need (e.g. `docs/definitions/output/track.json` for the track schema)
1818+- `docs/definitions/index.ts` — TypeScript types for all data structures
1919+2020+## Step 2 — Clarify intent
2121+2222+If the user hasn't described what the facet should do, ask one plain-language question before proceeding.
2323+2424+## Step 3 — Write the facet
2525+2626+Facets are HTML fragments (no `<!doctype>`, `<html>`, or `<head>`). The loader injects them into `<div id="container">` and sets a `<base>` pointing at the Diffuse build root, so all relative URLs resolve from there. The import map exposes `~/` as the root alias.
2727+2828+### Mandatory rules
2929+3030+- **`foundation.ready()`** must be called on every interface facet — it removes the loading spinner. Omitting it leaves the screen stuck on loading.
3131+- **`foundation.setup({ title })`** should be called to set the document title.
3232+- Always check the definitions fetched in Step 1 for the exact shape of any data you access — never assume top-level fields exist. For example, track metadata lives under `track.tags.*`, not at the top level.
3333+- Signal reader functions (`queue.now`, `queue.past`, `queue.future`, …) must be **called inside `effect()`** to be reactive.
3434+- Do **not** import modules with top-level `await` from Worker scripts — it causes RPC messages to be dropped.
3535+- Use **`@param` annotations above functions**, not inline `@type` in parameter lists.
3636+3737+### Skeleton
3838+3939+```html
4040+<style>
4141+ @import "./styles/base.css";
4242+ @import "./styles/diffuse/facet.css";
4343+ @import "./vendor/@phosphor-icons/web/fill/style.css"; /* or /bold/ */
4444+4545+ @layer base, diffuse;
4646+4747+ /* facet-specific styles */
4848+</style>
4949+5050+<main>
5151+ <!-- markup -->
5252+</main>
5353+5454+<script type="module">
5555+ import foundation from "~/common/foundation.js";
5656+ import { effect } from "~/common/signal.js";
5757+5858+ foundation.setup({ title: "My Facet | Diffuse" });
5959+6060+ // wire up elements …
6161+6262+ foundation.ready();
6363+</script>
6464+```
6565+6666+### Standard two-column layout
6767+6868+```html
6969+<main>
7070+ <div class="facet__left">
7171+ <a href="./dashboard/" class="diffuse-logo-container">
7272+ <svg viewBox="0 0 902 134" width="160">
7373+ <title>Diffuse</title>
7474+ <use href="images/diffuse-current.svg#diffuse"></use>
7575+ </svg>
7676+ </a>
7777+ <h1>Title</h1>
7878+ <p>Description.</p>
7979+ </div>
8080+ <div class="facet__right">
8181+ <!-- main content -->
8282+ </div>
8383+</main>
8484+```
8585+8686+For a centered or full-screen layout (player, dialog, etc.) override `body` and `main` in the facet's `<style>` block directly.
8787+8888+### Foundation API quick reference
8989+9090+```js
9191+// Engines
9292+const audio = await foundation.engine.audio();
9393+const queue = await foundation.engine.queue();
9494+const repeatShuffle = await foundation.engine.repeatShuffle();
9595+9696+// Configurators
9797+const inputCfg = await foundation.configurator.input();
9898+const metadataCfg = await foundation.configurator.metadata();
9999+100100+// Orchestrators
101101+const output = await foundation.orchestrator.output();
102102+const sources = await foundation.orchestrator.sources();
103103+const controller = await foundation.orchestrator.controller();
104104+const queueAudio = await foundation.orchestrator.queueAudio();
105105+const mediaSession = await foundation.orchestrator.mediaSession();
106106+const processTracks = await foundation.orchestrator.processTracks({ disableWhenReady: false });
107107+const favourites = await foundation.orchestrator.favourites();
108108+const artwork = await foundation.orchestrator.artwork();
109109+const scopedTracks = await foundation.orchestrator.scopedTracks();
110110+const autoQueue = await foundation.orchestrator.autoQueue();
111111+```
112112+113113+Though make sure to check the mentioned foundation js file for the latest code.
114114+115115+Typical playback bootstrap:
116116+117117+```js
118118+await foundation.orchestrator.queueAudio();
119119+await foundation.orchestrator.mediaSession();
120120+121121+const [audio, ctl, queue] = await Promise.all([
122122+ foundation.engine.audio(),
123123+ foundation.orchestrator.controller(),
124124+ foundation.engine.queue(),
125125+]);
126126+127127+await customElements.whenDefined(ctl.localName);
128128+```
129129+130130+### Reactivity
131131+132132+Signals are used for reactivity, see the `~/common/signal.js` javascript file for the code. It's based on the alien-signals library.
133133+134134+```js
135135+effect(() => {
136136+ const track = ctl.currentTrack(); // computed — call like a fn
137137+ const isPlaying = ctl.isPlaying();
138138+ const audioState = ctl.audio(); // AudioStateReadOnly | undefined
139139+140140+ if (audioState) {
141141+ const progress = audioState.progress(); // 0–1
142142+ const current = audioState.currentTime();
143143+ const duration = audioState.duration();
144144+ }
145145+146146+ const now = queue.now(); // SignalReader — call like a fn
147147+ const past = queue.past();
148148+ const future = queue.future();
149149+});
150150+```
151151+152152+### Audio control
153153+154154+```js
155155+audio.play({ audioId: queue.now().id });
156156+audio.pause({ audioId: queue.now().id });
157157+audio.seek({ audioId: queue.now().id, percentage: 0.5 }); // 0–1
158158+queue.shift(); // next track
159159+queue.unshift(); // previous track
160160+```
161161+162162+## Step 4 — Deliver
163163+164164+Output the complete facet HTML in a code block. Tell the user to open the `code/` page in Diffuse, paste it in, and load it.