From 4f0d2fd1108970daf8e6ca5e2ee1baccd70d5d67 Mon Sep 17 00:00:00 2001 From: Lukce Date: Thu, 24 Sep 2026 13:08:35 -0400 Subject: [PATCH] v1.4.0: a Custom Elements Manifest webcomponents.org does not take pull requests. Its catalog server indexes npm packages and reads their Custom Elements Manifest, so the listing is not a submission, it is a file in the package. Adding it is what makes us indexable. It earns its place either way. The same manifest is what VS Code and the JetBrains IDEs read for tag and attribute autocomplete, so anyone who installs the package gets completion on data-position, data-llms and the rest without looking anything up. The manifest documents the two CSS custom properties as well as the attributes, including the part a host gets wrong: --hms-top and --hms-bottom have to be set together, because a fixed box with both edges set and height auto stretches. Two tests pin it to the component. Every attribute the manifest advertises must actually be read by the source, and every custom property it documents must appear there. A manifest that drifts is worse than none: it describes attributes that do nothing, in the one place a tool trusts without checking. 34 tests. --- custom-elements.json | 75 +++++++++++++++++++++++++++++++++++++++++ package.json | 6 ++-- test/component.test.mjs | 30 +++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 custom-elements.json diff --git a/custom-elements.json b/custom-elements.json new file mode 100644 index 0000000..f3d21ad --- /dev/null +++ b/custom-elements.json @@ -0,0 +1,75 @@ +{ + "schemaVersion": "2.1.0", + "readme": "README.md", + "modules": [ + { + "kind": "javascript-module", + "path": "src/human-machine-swapper.js", + "declarations": [ + { + "kind": "class", + "customElement": true, + "tagName": "human-machine-swapper", + "name": "HumanMachineSwapper", + "description": "A Human / Machine pill. Machine shows the site's llms.txt, the plain-text reading written for language models, rendered in place over the page. Renders nothing at all when the site has no llms.txt to offer.", + "attributes": [ + { + "name": "data-position", + "description": "Which edge the pill is fixed to.", + "type": { "text": "'bottom-center' | 'top-center'" }, + "default": "'bottom-center'" + }, + { + "name": "data-llms", + "description": "The machine reading to link to. 'auto' reads , then probes /llms.txt and /llms-full.txt. When nothing answers, the component renders nothing and explains why in the console.", + "type": { "text": "'auto' | string" }, + "default": "'auto'" + }, + { + "name": "data-accent", + "description": "Accent colour for the current dot and hover state.", + "type": { "text": "string" }, + "default": "'#a8f326'" + }, + { + "name": "data-labels-human", + "description": "Overrides the Human label.", + "type": { "text": "string" }, + "default": "'Human'" + }, + { + "name": "data-labels-machine", + "description": "Overrides the Machine label.", + "type": { "text": "string" }, + "default": "'Machine'" + } + ], + "cssProperties": [ + { + "name": "--hms-top", + "description": "Distance from the top edge. Set this AND --hms-bottom together when overriding: a fixed box with both set and height auto stretches. Use when the site's own chrome occupies the default edge.", + "default": "calc(14px + env(safe-area-inset-top)) when data-position is top-center, otherwise auto" + }, + { + "name": "--hms-bottom", + "description": "Distance from the bottom edge. See --hms-top.", + "default": "calc(20px + env(safe-area-inset-bottom)) when data-position is bottom-center, otherwise auto" + } + ], + "members": [], + "events": [] + } + ], + "exports": [ + { + "kind": "custom-element-definition", + "name": "human-machine-swapper", + "declaration": { + "name": "HumanMachineSwapper", + "module": "src/human-machine-swapper.js" + } + } + ] + } + ] +} diff --git a/package.json b/package.json index 3934390..332c6f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "human-machine-swapper", - "version": "1.3.0", + "version": "1.4.0", "description": "A web component that adds a Human / Machine pill to any website. Machine shows the site's llms.txt, the plain-text reading written for language models, rendered in place. One file, about 6 KB gzipped, no dependencies and no build step.", "keywords": [ "llms.txt", @@ -41,6 +41,7 @@ "bin", "src", "agent", + "custom-elements.json", "llms.txt", "AGENTS.md", "README.md", @@ -59,5 +60,6 @@ }, "bin": { "human-machine-swapper": "bin/cli.mjs" - } + }, + "customElements": "custom-elements.json" } diff --git a/test/component.test.mjs b/test/component.test.mjs index df1195c..3191e18 100644 --- a/test/component.test.mjs +++ b/test/component.test.mjs @@ -82,3 +82,33 @@ test("the published tarball carries the files an agent reads", () => { assert.ok(PKG.files.includes(f), `${f} is missing from package.json files`); } }); + +/** + * The Custom Elements Manifest is what webcomponents.org indexes and what an IDE reads for + * autocomplete. A manifest that drifts from the component is worse than none: it documents + * attributes that do nothing, in the one place a tool trusts without checking. + */ +test("the custom elements manifest matches the component", () => { + const manifest = JSON.parse(readFileSync(join(ROOT, "custom-elements.json"), "utf8")); + const decl = manifest.modules[0].declarations[0]; + + assert.equal(decl.tagName, "human-machine-swapper"); + assert.equal(manifest.modules[0].path, PKG.main, "manifest path must match the entry point"); + + // Every attribute the manifest advertises must actually be read by the source. + for (const attr of decl.attributes) { + assert.ok( + SOURCE.includes(`"${attr.name}"`), + `manifest advertises ${attr.name}, which the component never reads` + ); + } + // And every custom property it documents must be used. + for (const prop of decl.cssProperties) { + assert.ok(SOURCE.includes(prop.name), `manifest documents ${prop.name}, unused in source`); + } +}); + +test("package.json points at the manifest and ships it", () => { + assert.equal(PKG.customElements, "custom-elements.json"); + assert.ok(PKG.files.includes("custom-elements.json")); +});