diff --git a/_config.ts b/_config.ts index f218148a..e271bc1e 100644 --- a/_config.ts +++ b/_config.ts @@ -1,5 +1,3 @@ -import type { RequestHandler } from "lume/core/server.ts"; - import { dotenvRun } from "@dotenv-run/esbuild"; import lume from "lume/mod.ts"; @@ -16,6 +14,8 @@ import { wasmLoader } from "esbuild-plugin-wasm"; import autoprefixer from "autoprefixer"; import cssnano from "cssnano"; +import { Uint8ArrayReader, Uint8ArrayWriter, ZipWriter } from "@zip-js/zip-js"; + import { create as createCID } from "~/common/cid.js"; const site = lume({ @@ -134,7 +134,7 @@ site.add([".js"]); // *.inline.js files are inlined into their companion HTML at build/serve time. // Exclude them from the regular build so esbuild doesn't try to bundle them. -site.ignore((p) => p.endsWith(".inline.js")); +site.ignore((p) => p.endsWith(".inline.js") || p.endsWith("SKILL.md")); //////////////////////////////////////////// // CSS @@ -277,13 +277,6 @@ site.add([".html"]); site.add([".json"]); site.add([".webmanifest"]); -site.remoteFile( - "architecture.txt", - import.meta.resolve("./docs/ARCHITECTURE.md"), -); - -site.add("architecture.txt"); - site.script("copy-type-defs", () => { for ( const f of walkSync( @@ -298,8 +291,37 @@ site.script("copy-type-defs", () => { } }); -site.addEventListener("afterBuild", () => { - // site.run("copy-type-defs"); +// SKILLS + +site.remoteFile( + "skills/diffuse-facet/docs/architecture.txt", + import.meta.resolve("./docs/ARCHITECTURE.md"), +); + +site.remoteFile( + "skills/diffuse-facet/example/index.html", + import.meta.resolve("./src/facets/data/sources/index.html"), +); + +site.add("skills/diffuse-facet/docs/architecture.txt"); +site.add("skills/diffuse-facet/example/index.html"); +site.add("/definitions", "/skills/diffuse-facet/docs/definitions"); +site.copy("skills/diffuse-facet/SKILL.md"); +site.add("skills"); + +site.addEventListener("afterBuild", async () => { + const skillsDir = "dist/skills/diffuse-facet"; + const zipWriter = new ZipWriter(new Uint8ArrayWriter()); + + for (const entry of walkSync(skillsDir, { includeDirs: false })) { + if (entry.path.endsWith(".br")) continue; + await zipWriter.add( + "diffuse-facet/" + entry.path.slice(skillsDir.length + 1), + new Uint8ArrayReader(Deno.readFileSync(entry.path)), + ); + } + + Deno.writeFileSync("dist/skills/diffuse-facet.zip", await zipWriter.close()); }); //////////////////////////////////////////// diff --git a/deno.jsonc b/deno.jsonc index 86d7e48e..050c9ea0 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -38,6 +38,7 @@ "@std/semver": "jsr:@std/semver@^1.0.8", "@std/xml": "jsr:@std/xml@^0.1.0", "@vicary/debounce-microtask": "jsr:@vicary/debounce-microtask@^0.1.8", + "@zip-js/zip-js": "jsr:@zip-js/zip-js@^2", "@tanstack/virtual-core": "npm:@tanstack/virtual-core@^3.13.0", "alien-signals": "npm:alien-signals@^3.1.2", "bs58check": "npm:bs58check@^4.0.0", diff --git a/src/skills/diffuse-facet/SKILL.md b/src/skills/diffuse-facet/SKILL.md new file mode 100644 index 00000000..b95b9b42 --- /dev/null +++ b/src/skills/diffuse-facet/SKILL.md @@ -0,0 +1,164 @@ +--- +name: diffuse-facet +description: Create an interface or feature facet for Diffuse +user-invocable: true +version: 0.1.0 +--- + +Create a Diffuse facet and produce the HTML ready to paste into the `code/` page. + +## Step 1 — Read the docs + +Use the read tool to read these files: + +- `docs/architecture.txt` — system overview, facet rules, foundation API +- `docs/elements.txt` — all available custom elements with code examples +- `example/index.html` — a representative interface facet to use as a reference +- Any specific definition you need (e.g. `docs/definitions/output/track.json` for the track schema) +- `docs/definitions/index.ts` — TypeScript types for all data structures + +## Step 2 — Clarify intent + +If the user hasn't described what the facet should do, ask one plain-language question before proceeding. + +## Step 3 — Write the facet + +Facets are HTML fragments (no ``, ``, or ``). The loader injects them into `
` and sets a `` pointing at the Diffuse build root, so all relative URLs resolve from there. The import map exposes `~/` as the root alias. + +### Mandatory rules + +- **`foundation.ready()`** must be called on every interface facet — it removes the loading spinner. Omitting it leaves the screen stuck on loading. +- **`foundation.setup({ title })`** should be called to set the document title. +- 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. +- Signal reader functions (`queue.now`, `queue.past`, `queue.future`, …) must be **called inside `effect()`** to be reactive. +- Do **not** import modules with top-level `await` from Worker scripts — it causes RPC messages to be dropped. +- Use **`@param` annotations above functions**, not inline `@type` in parameter lists. + +### Skeleton + +```html + + +
+ +
+ + +``` + +### Standard two-column layout + +```html +
+
+ + + Diffuse + + + +

Title

+

Description.

+
+
+ +
+
+``` + +For a centered or full-screen layout (player, dialog, etc.) override `body` and `main` in the facet's `