A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add facet skill

+200 -13
+34 -12
_config.ts
··· 1 - import type { RequestHandler } from "lume/core/server.ts"; 2 - 3 1 import { dotenvRun } from "@dotenv-run/esbuild"; 4 2 import lume from "lume/mod.ts"; 5 3 ··· 15 13 import { wasmLoader } from "esbuild-plugin-wasm"; 16 14 import autoprefixer from "autoprefixer"; 17 15 import cssnano from "cssnano"; 16 + 17 + import { Uint8ArrayReader, Uint8ArrayWriter, ZipWriter } from "@zip-js/zip-js"; 18 18 19 19 import { create as createCID } from "~/common/cid.js"; 20 20 ··· 134 134 135 135 // *.inline.js files are inlined into their companion HTML at build/serve time. 136 136 // Exclude them from the regular build so esbuild doesn't try to bundle them. 137 - site.ignore((p) => p.endsWith(".inline.js")); 137 + site.ignore((p) => p.endsWith(".inline.js") || p.endsWith("SKILL.md")); 138 138 139 139 //////////////////////////////////////////// 140 140 // CSS ··· 277 277 site.add([".json"]); 278 278 site.add([".webmanifest"]); 279 279 280 - site.remoteFile( 281 - "architecture.txt", 282 - import.meta.resolve("./docs/ARCHITECTURE.md"), 283 - ); 284 - 285 - site.add("architecture.txt"); 286 - 287 280 site.script("copy-type-defs", () => { 288 281 for ( 289 282 const f of walkSync( ··· 298 291 } 299 292 }); 300 293 301 - site.addEventListener("afterBuild", () => { 302 - // site.run("copy-type-defs"); 294 + // SKILLS 295 + 296 + site.remoteFile( 297 + "skills/diffuse-facet/docs/architecture.txt", 298 + import.meta.resolve("./docs/ARCHITECTURE.md"), 299 + ); 300 + 301 + site.remoteFile( 302 + "skills/diffuse-facet/example/index.html", 303 + import.meta.resolve("./src/facets/data/sources/index.html"), 304 + ); 305 + 306 + site.add("skills/diffuse-facet/docs/architecture.txt"); 307 + site.add("skills/diffuse-facet/example/index.html"); 308 + site.add("/definitions", "/skills/diffuse-facet/docs/definitions"); 309 + site.copy("skills/diffuse-facet/SKILL.md"); 310 + site.add("skills"); 311 + 312 + site.addEventListener("afterBuild", async () => { 313 + const skillsDir = "dist/skills/diffuse-facet"; 314 + const zipWriter = new ZipWriter(new Uint8ArrayWriter()); 315 + 316 + for (const entry of walkSync(skillsDir, { includeDirs: false })) { 317 + if (entry.path.endsWith(".br")) continue; 318 + await zipWriter.add( 319 + "diffuse-facet/" + entry.path.slice(skillsDir.length + 1), 320 + new Uint8ArrayReader(Deno.readFileSync(entry.path)), 321 + ); 322 + } 323 + 324 + Deno.writeFileSync("dist/skills/diffuse-facet.zip", await zipWriter.close()); 303 325 }); 304 326 305 327 ////////////////////////////////////////////
+1
deno.jsonc
··· 38 38 "@std/semver": "jsr:@std/semver@^1.0.8", 39 39 "@std/xml": "jsr:@std/xml@^0.1.0", 40 40 "@vicary/debounce-microtask": "jsr:@vicary/debounce-microtask@^0.1.8", 41 + "@zip-js/zip-js": "jsr:@zip-js/zip-js@^2", 41 42 "@tanstack/virtual-core": "npm:@tanstack/virtual-core@^3.13.0", 42 43 "alien-signals": "npm:alien-signals@^3.1.2", 43 44 "bs58check": "npm:bs58check@^4.0.0",
+1 -1
src/elements.txt.vto src/skills/diffuse-facet/docs/elements.txt.vto
··· 1 1 --- 2 - url: /elements.txt 2 + url: /skills/diffuse-facet/docs/elements.txt 3 3 layout: false 4 4 --- 5 5 # Diffuse Elements
+164
src/skills/diffuse-facet/SKILL.md
··· 1 + --- 2 + name: diffuse-facet 3 + description: Create an interface or feature facet for Diffuse 4 + user-invocable: true 5 + version: 0.1.0 6 + --- 7 + 8 + Create a Diffuse facet and produce the HTML ready to paste into the `code/` page. 9 + 10 + ## Step 1 — Read the docs 11 + 12 + Use the read tool to read these files: 13 + 14 + - `docs/architecture.txt` — system overview, facet rules, foundation API 15 + - `docs/elements.txt` — all available custom elements with code examples 16 + - `example/index.html` — a representative interface facet to use as a reference 17 + - Any specific definition you need (e.g. `docs/definitions/output/track.json` for the track schema) 18 + - `docs/definitions/index.ts` — TypeScript types for all data structures 19 + 20 + ## Step 2 — Clarify intent 21 + 22 + If the user hasn't described what the facet should do, ask one plain-language question before proceeding. 23 + 24 + ## Step 3 — Write the facet 25 + 26 + 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. 27 + 28 + ### Mandatory rules 29 + 30 + - **`foundation.ready()`** must be called on every interface facet — it removes the loading spinner. Omitting it leaves the screen stuck on loading. 31 + - **`foundation.setup({ title })`** should be called to set the document title. 32 + - 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. 33 + - Signal reader functions (`queue.now`, `queue.past`, `queue.future`, …) must be **called inside `effect()`** to be reactive. 34 + - Do **not** import modules with top-level `await` from Worker scripts — it causes RPC messages to be dropped. 35 + - Use **`@param` annotations above functions**, not inline `@type` in parameter lists. 36 + 37 + ### Skeleton 38 + 39 + ```html 40 + <style> 41 + @import "./styles/base.css"; 42 + @import "./styles/diffuse/facet.css"; 43 + @import "./vendor/@phosphor-icons/web/fill/style.css"; /* or /bold/ */ 44 + 45 + @layer base, diffuse; 46 + 47 + /* facet-specific styles */ 48 + </style> 49 + 50 + <main> 51 + <!-- markup --> 52 + </main> 53 + 54 + <script type="module"> 55 + import foundation from "~/common/foundation.js"; 56 + import { effect } from "~/common/signal.js"; 57 + 58 + foundation.setup({ title: "My Facet | Diffuse" }); 59 + 60 + // wire up elements … 61 + 62 + foundation.ready(); 63 + </script> 64 + ``` 65 + 66 + ### Standard two-column layout 67 + 68 + ```html 69 + <main> 70 + <div class="facet__left"> 71 + <a href="./dashboard/" class="diffuse-logo-container"> 72 + <svg viewBox="0 0 902 134" width="160"> 73 + <title>Diffuse</title> 74 + <use href="images/diffuse-current.svg#diffuse"></use> 75 + </svg> 76 + </a> 77 + <h1>Title</h1> 78 + <p>Description.</p> 79 + </div> 80 + <div class="facet__right"> 81 + <!-- main content --> 82 + </div> 83 + </main> 84 + ``` 85 + 86 + For a centered or full-screen layout (player, dialog, etc.) override `body` and `main` in the facet's `<style>` block directly. 87 + 88 + ### Foundation API quick reference 89 + 90 + ```js 91 + // Engines 92 + const audio = await foundation.engine.audio(); 93 + const queue = await foundation.engine.queue(); 94 + const repeatShuffle = await foundation.engine.repeatShuffle(); 95 + 96 + // Configurators 97 + const inputCfg = await foundation.configurator.input(); 98 + const metadataCfg = await foundation.configurator.metadata(); 99 + 100 + // Orchestrators 101 + const output = await foundation.orchestrator.output(); 102 + const sources = await foundation.orchestrator.sources(); 103 + const controller = await foundation.orchestrator.controller(); 104 + const queueAudio = await foundation.orchestrator.queueAudio(); 105 + const mediaSession = await foundation.orchestrator.mediaSession(); 106 + const processTracks = await foundation.orchestrator.processTracks({ disableWhenReady: false }); 107 + const favourites = await foundation.orchestrator.favourites(); 108 + const artwork = await foundation.orchestrator.artwork(); 109 + const scopedTracks = await foundation.orchestrator.scopedTracks(); 110 + const autoQueue = await foundation.orchestrator.autoQueue(); 111 + ``` 112 + 113 + Though make sure to check the mentioned foundation js file for the latest code. 114 + 115 + Typical playback bootstrap: 116 + 117 + ```js 118 + await foundation.orchestrator.queueAudio(); 119 + await foundation.orchestrator.mediaSession(); 120 + 121 + const [audio, ctl, queue] = await Promise.all([ 122 + foundation.engine.audio(), 123 + foundation.orchestrator.controller(), 124 + foundation.engine.queue(), 125 + ]); 126 + 127 + await customElements.whenDefined(ctl.localName); 128 + ``` 129 + 130 + ### Reactivity 131 + 132 + Signals are used for reactivity, see the `~/common/signal.js` javascript file for the code. It's based on the alien-signals library. 133 + 134 + ```js 135 + effect(() => { 136 + const track = ctl.currentTrack(); // computed — call like a fn 137 + const isPlaying = ctl.isPlaying(); 138 + const audioState = ctl.audio(); // AudioStateReadOnly | undefined 139 + 140 + if (audioState) { 141 + const progress = audioState.progress(); // 0–1 142 + const current = audioState.currentTime(); 143 + const duration = audioState.duration(); 144 + } 145 + 146 + const now = queue.now(); // SignalReader — call like a fn 147 + const past = queue.past(); 148 + const future = queue.future(); 149 + }); 150 + ``` 151 + 152 + ### Audio control 153 + 154 + ```js 155 + audio.play({ audioId: queue.now().id }); 156 + audio.pause({ audioId: queue.now().id }); 157 + audio.seek({ audioId: queue.now().id, percentage: 0.5 }); // 0–1 158 + queue.shift(); // next track 159 + queue.unshift(); // previous track 160 + ``` 161 + 162 + ## Step 4 — Deliver 163 + 164 + 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.