Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions custom-elements.json
Original file line number Diff line number Diff line change
@@ -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 <link rel=\"llms\">, 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"
}
}
]
}
]
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -41,6 +41,7 @@
"bin",
"src",
"agent",
"custom-elements.json",
"llms.txt",
"AGENTS.md",
"README.md",
Expand All @@ -59,5 +60,6 @@
},
"bin": {
"human-machine-swapper": "bin/cli.mjs"
}
},
"customElements": "custom-elements.json"
}
30 changes: 30 additions & 0 deletions test/component.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
});
Loading