Repository navigation
Releases: rhnfzl/human-html
Release list
v2.0.0
Breaking. data-audience is retired rather than aliased and now blocks, so a post-cutoff artifact still carrying the pre-rename summary marker fails until it is changed to data-summary="true". Artifacts dated before 2026-05-25 are grandfathered and unaffected. The summary itself does not change, only the attribute.
Ownership attaches to the claim rather than the artifact, every summary says what would change its own conclusion, and no surface an author reads or copies names a job title any more. Length gains the ceiling required-section never had, read-time is checked against the prose instead of trusted, and the review state has three names. Four rounds of independent review are folded in, each of which found real defects in the previous round's fixes.
- House rule: the prompt lives in the provenance footer, never in a card at the top (
references/patterns.md). A visible "the prompt that made this" block with a copy button looks like transparency and costs the most valuable space on the page on something the reader did not come for; they opened a plan to find out whether the plan is sound.promptHashin the footer already makes the artifact auditable. The rule is about placement, not concealment: put the full prompt in the footer whenever it is non-sensitive, which is more transparent than a card and costs the reader nothing. - Files read (
references/patterns.md, optional, best inunderstanding/research/review). An artifact explaining code makes claims about a codebase that keeps moving, and six weeks later nobody can tell which claims still hold. A collapsed<details>block lists the files consulted and the revision they were read at, which converts the question into a command:git diff --stat <rev> -- <paths>, and empty output means nothing the artifact read has changed since, in history or in the reader's working copy. Deliberately not<rev>..HEAD, which compares two commits and so reports an uncommitted edit as clean; both forms were run against a planted uncommitted edit to confirm which one catches it. The revision is the whole pattern, socheckwarns when asourceFilesblock omitsreadAtRevisionor ships an empty path list, and stays silent when there is no block at all. The pathspec may be wider than the list and must never be narrower, which is the one way this pattern fails while looking fine: a trimmed pathspec still prints nothing while a file a claim rests on has moved. Mirrored into the provenance JSON-LD assourceFilesso it is machine-checkable, and demonstrated in two shipped examples:understanding-canonical.html(three files it traces a request through) anddynamic-port-correspondence.html(five files across a port, all read at one branch tip that carries both implementations, with a pathspec widened on purpose because a correspondence map is also wrong when a sixth file appears that nobody mapped, said in a clause so the mismatch does not read as a slip). Two tests run over every example carrying the block rather than one named file, so a new one is covered on arrival: the visible list, the<summary>count and the JSON-LD must agree, and the pathspec must contain every path listed. Deliberately NOT added toreview,research,plan,status,incidentorprototype: those examples cite no file paths at all, and the pattern's own rule is to list what was read rather than what exists. Needs no CSS; the scaffold already stylesdetails. - The files-read guards could pass while the block was wrong, and the snippet people copy broke a rule printed beside it. Three findings from the Codex second eye, all reproduced before being accepted and all reproduced again as failures afterwards. First, the revision was matched anywhere in the block, so the
<summary>could drift to a different revision while the command still carried the declared one; the summary is the revision the reader actually reads, so it and the command are now checked separately. Second, the path regex demanded exactly<li><code>path</code></li>, so a visible entry the JSON-LD never declared could hide behind an attribute; attributes are tolerated now, and a count assertion fails when any<li>is not read as a path, which also catches a path written without<code>. Both guards were sharing nothing, which is how they drifted, so they now share one parser. Third, thepatterns.mdsnippet listedroutes/*.tsand diffed the widerroutes/, costing every copier a false alarm whenever an undeclaredroutes/README.mdchanged. The snippet is now held tighter than a shipped artifact, its pathspec having to equal its declared paths rather than merely contain them, because it is the copy source and a bare widening there teaches the exception as the default. Widening stays legal for an artifact, demonstrated with its explaining clause indynamic-port-correspondence.html. A new test holds the snippet to this, since nothing checked it at all before. - A second reviewer pass found four more ways the guards were wrong, two of them in the fixes for the first pass. Each reproduced before being accepted. A revision was matched by substring, so a declared
mainwas satisfied by a summary readingdomainand by a command touchingdomain/file.ts;readAtRevisionis any non-empty string and so may be a branch or tag rather than a hex SHA, which makes that collision reachable rather than theoretical. Both revisions are now compared as exact tokens. Only the first files-read block in a file was ever parsed, so a second would go unchecked, which the guards now assert against rather than quietly inspecting one block out of several. The rule holding thepatterns.mdsnippet to its declared paths compared ordered lists, so the same paths in a different order failed while covering the identical scope; order is no longer checked, and a genuinely trimmed pathspec still fails. And the claim that empty output proves nothing changed in history was too strong in the other direction:git diff <rev> -- <paths>compares content at the two ends, so a file changed and later reverted also reads as clean. That is the right answer for staleness and the wrong claim about history, so both the pattern and the example now say "still matches what was read". - Same-data control block (
references/patterns.md). A multi-option comparison has a failure mode that is invisible from outside: each option gets measured against whichever input flatters it, and the table at the end looks like a fair scoreboard while being four anecdotes in a grid. One block before the first option names the single input every option is judged against, says which way it is biased and confirms the bias falls evenly, states the properties that actually separate the designs rather than just the volume, and asserts that every number below comes from that input.dynamic-design-directions.htmlshipped as the worked example a release earlier; this documents it so it can be reached deliberately. The snippet carries the two CSS rules it needs, both drawn from scaffold variables, because.controland.fixedare the example's own and copying the markup alone would render unstyled. - Ownership attaches to the claim, not to the artifact (Rule 11,
claim-owner, WARN). A recommendation nobody holds is the artifact arguing on its own behalf, and a reader has nothing to push back against. The rule is forced by the two kinds that carry both description and judgment: anarchitecturereports Before/After then recommends, areviewreports Concerns then issues a Verdict, and both split down the same seam. Attaching ownership below the level where they split dissolves the problem rather than assigning it by fiat.data-owneron the section, plus the holder named in prose beside one sentence of their own doubt, because a name alone is bureaucracy and a name with a stated doubt tells a reviewer where to spend attention. No kind list: a heading regex selects the judgment section exactly ascomparison-visualselects a comparison, so kind sensitivity emerges instead of being declared.researchandunderstandingcarry no such heading and never trip it;decisionalways does. In force in dynamic mode too, since who holds a call does not depend on which sections exist.statusis the deliberate gap, its accountability being artifact-wide and already carried by the ribbon'sOwner. The search window is the subtle part and is what the tests target: an owner sits on the section's opening tag, before the heading, so the window cannot start wherecomparison-visual's does, and it is floored at the previous heading so a neighbour's owner cannot satisfy an unowned section. A first cut of the marker regex accepteddata-owner="", because\Smatched the closing quote; the test written for that case is what caught it. - The reviewer field starts empty and says so. The scaffold shipped
reviewer: "<human reviewer or role>"and rendered "reviewed by <reviewer>", which reads as a claim that review happened. It now ships"pending"and renders not yet reviewed by a human. Measured on a live lane of 197 artifacts before the change: 89 carried a human name, 51 carried no reviewer field at all, 29 carried an agent name, 18 carried some spelling of "pending" across nine distinct spellings, and 10 shipped the unfilled placeholder, which is an author hand-rolling a state the schema never offered. An agent name there is honest reporting rather than a mistake; the schema simply has no word for it yet. The shipped examples keep their reviewer names on purpose: they show the end of the lifecycle, the scaffold shows the start. - Every summary states what would change its own conclusion (Rule 1, fourth bullet, deliberately not validated). The first three bullets orient and none of them concludes, so a reader who cannot tell whether the opening is complete has one safe move, which i...
v1.3.0
Artifacts gain a dynamic mode for the cases where a prescribed section skeleton makes the document worse, and a written spine covering what an artifact must never do whatever shape it takes. Rule 9 (no-JS robustness) becomes verifiable instead of assumed. Hook automation is one idempotent command. Three contract defects in the shipped examples are fixed, and the summary marker stops naming a job title.
Added
artifact-mode="dynamic". The nine kinds pre-decide a section skeleton before anyone knows what the artifact needs to say, which is the right trade most of the time and the wrong one when the shape is the argument (a findings taxonomy, a four-way exploration, a line-by-line port map). Adding<meta name="artifact-mode" content="dynamic">beside the kind, or scaffolding withnew <kind> "<title>" --mode dynamic, stands down exactly three rules and nothing else:required-section(off),read-map(off), andnav-anchors(BLOCK to WARN). Mode is a separate field from kind on purpose, so the kind still says what the document is for and drives the gallery and the filename, and aplancan go dynamic when the skeleton stops fitting. Every other rule is untouched, because the rule set was always two populations wearing one coat:required-sectionsays a plan looks like this,viewport-metasays a human on a phone can read this, and only the first is about shape. The mode-selection rule is one question, does the reader benefit from this looking like the last one, with the kind as the tie-break, since a kind that fits slightly badly costs a reader less than an invented structure that fits nothing.references/artifact-spine.md. The rules say what an artifact must contain; nothing said what it must never do, and a mode defined only by what it removes drifts straight to a model's defaults. Three layers: the mechanical floor (plus the requirements that cannot be linted, colour never carrying meaning alone, hover always paired with focus and touch, and a scroll container always reachable by keyboard), the spine an artifact never trades away in any mode (no coined framework names, never name or segment a reader, no engineered keystone, hedges stay, every claim earned, credibility from this artifact's own evidence, avoid the first person, never stage common knowledge as a reveal, inherit the tokens), and prose discipline (after every list one sentence on what it implies; name the concrete failure not the category; small precise numbers over adjectives; one canonical noun per thing; no participle analysis tails; vary the block rhythm). Dynamic mode is free in structure and locked in style. The floor table states what each check actually proves next to what it aims at, because every one of them is a marker check.render --no-js. Rule 9 protects readers in JavaScript-disabled previews (iOS Quick Look, Android file previews, email) and was the one rule the harness could not check:js-content-fallbackonly greps for a<noscript>, so it cannot tell a control from content and cannot read whether the fallback matches what the script would build.render --no-jsserves the page as a JS-disabled browser treats it and screenshots the result, so you can look. No Chrome flag does this, which is why it is a rewrite:--disable-javascriptis accepted and silently ignored (its screenshot is byte-identical both to a run with JS on and to a run with a deliberately invented flag), and--blink-settings=scriptEnabled=falsemakes headless Chrome emit no output at all.simulate_no_js()reproduces the only two things disabling JS changes, and says so plainly rather than claiming to be the browser's own setting.- Three dynamic examples that share no structure on purpose (
examples/dynamic-*.html), across three different kinds, so no single shape reads as the template a dynamic artifact should copy. A test fails if any two of them converge on a shared kind or heading set. - Global hook activation.
python3 <skill-dir>/activate_hooks.pymerges the advisory and autoindex hooks into Claude Code, Codex, Cursor, and Windsurf settings without replacing unrelated configuration. - Windsurf support. A thin adapter translates Cascade's write and command hook payloads into the existing agent-neutral hook input.
- Discoverable wiring metadata. The shipped hooks include their event and matcher headers for downstream installers.
- Implementation deviation ledger (
references/workflow-integrations.md). The during-the-build log that had no home:planis written before the work andreviewhappens after it, so everything the code teaches you mid-build evaporates into agent scrollback and the next attempt rediscovers it. Four fixed fields per entry, what the plan said, what the code revealed, the conservative choice taken, what to revisit, closing with a mandatory fold-forward block that turns the surprises into three instructions for the next attempt. Uses the existingstatuskind; no new artifact kind and no validator rule. - Reader-response compiler (
references/workflow-integrations.md). Most artifacts end with the reader having read; this one ends with them having replied, as a block they paste straight back. The no-JS shape is prescribed rather than suggested, because the obvious implementation renders as dead controls and an empty box in exactly the previews Rule 9 exists to protect. - Review order vs execution order (
references/patterns.md). Aplansorted by likelihood-of-tweaking rather than build order, so every judgment call is up front and the mechanical work collapses into<details>. Includes flagging the weakest part of your own plan, and pre-writing the likeliest pushbacks so the reader can send one in a click. - Non-goals (
references/patterns.md). "What we deliberately did not do", written as the omission plus its reason. Pre-empts the whole class of review comment that begins "why didn't you also...". Distinguished from Open questions: a non-goal is settled, an open question needs an owner and a date. - Linked source-to-target correspondence (
references/patterns.md). For ports and migrations: corresponding regions in the source and target excerpts share a token and a number, with the trap in a numbered margin note, plus a preserved / deliberately changed / dropped ledger. The numbered correspondence must carry the meaning with no pointer and no JS. - Pre-merge self-check (
references/patterns.md). A handful of questions attached to a review artifact, answerable only by someone who understood the change. Native<details>, so it works with JavaScript off. Explicitly not a gate and never scored: answering questions about a document is not evidence the change is safe, so the honest claim is the narrow one, that a reviewer who cannot answer these has not read what they are about to approve.
Changed
data-audience="pm"is nowdata-summary="true", and thepm-summaryrule issummary-first. The contract bans labelling reading depth by job title, and the nine canonical read maps were relabelled for that reason. The summary marker was the same segmentation one layer down in the markup, and it was the one rule that blocked unless you wrote it. The pre-rename attribute is still accepted so already-shipped artifacts keep validating, and retired rule IDs still answer to suppression comments, so nothing breaks on upgrade. The.pm-summaryclass is now.lead-summarythroughout, since leaving it behind means the next author copies the job title forward.
Fixed
js-content-fallbacknow detects DOM insertion, not one phrase. The rule matched only.innerHTML =, so content assembled withcreateElement+textContent+appendChildpassed silently, including in this skill's ownprototype-canonical.html, which shipped an empty<ul>filled by JS and no<noscript>, rendering blank in iOS Quick Look. Detection now coversinnerHTML/outerHTMLassignment,insertAdjacentHTML,replaceChildren,appendChild/append/prepend,insertBefore, anddocument.write, and deliberately not a barecreateElement(a detached node puts nothing on the page), so the JS-injected copy-button pattern stays clean. Also fixes a pre-existing false positive:.innerHTML ===(a read) matched as a write.prototype-canonical.htmlnever had a working static floor. It hid its inert slider and copy button through a<noscript>stylesheet rule, but those controls carry an inlinestyle="display:flex", and an inline declaration beats a stylesheet rule. A reader with JavaScript off saw a live-looking slider driving nothing, and it passedjs-content-fallbackthroughout because a<noscript>element was present. Fixed withdisplay: none !important; the trap is documented in SKILL.md and the spine, because anyone hiding an inline-styled control will hit it. Found byrender --no-json its first real use.- Renaming a rule broke every suppression written against its old name.
_addmatches the emitted rule ID exactly, so an installed workspace carrying<!-- human-html-disable: pm-summary -->went from passing to a blockingsummary-firsterror, with nothing in the message explaining why. Retired IDs now keep answering through_RULE_ID_ALIASES, guarded by a test that also asserts the alias does not silence the rule for artifacts that never suppressed it. Any future rename adds its alias there. - Reading guides in all nine canonical examples were role-based, which the contract forbids. SKILL.md requires depth-based guides and explicitly bans job-title labels, yet every example shipped
Exec (2 min)/PM (5 min)/Engineer (15 min). Because SKILL.md tells agents to read the canonical example before writing an artifact of that kind, the example was quietly overriding the rule. Relabelled toQuick read/Standard/Full read, keeping the time budgets. - **Mermaid lab...
v1.2.4
Fix a broken remote install: npx skills add rhnfzl/human-html now ships the full skill, not just SKILL.md. No change to generated artifacts.
- The skill moved into
skills/human-html/. Theskillsinstaller copies a skill's full payload only when it lives in a subdirectory; a root-level remote skill shippedSKILL.mdalone, dropping the engine, references, templates, examples, and hooks, so the installed skill told the agent to run a script and read files that were never installed. Relocating the skill intoskills/human-html/puts it on the subdirectory code path, so every support file lands. Mirrors theslide-sageandexplore-unknownslayout. - Re-install note for existing users. If a previous install left the skill referencing a script or reference files that were not present, re-run
npx skills add rhnfzl/human-html(or your installer's equivalent) to pull the complete payload. - Plugin channel unchanged. The Claude Code plugin still installs from the same marketplace (
source: "./"); the slash command and hooks resolve the relocated engine via$CLAUDE_PLUGIN_ROOT/skills/human-html/. Manual "clone and symlink" now targetsskills/human-html/, and the release version-guard reads the relocatedSKILL.md.
v1.2.3
Security-posture housekeeping. No functional change to generated artifacts.
- Add
SECURITY.md. Documents the skill's two intentional external touch-points and why they are safe: the optional bring-your-own-bucket S3 publish helper (uploads to a bucket you own, then prints a link - never downloads or executes), and the optional client-side, version-pinned, self-gating Mermaid CDN import used only for live (non-shipped) diagrams. Gives human auditors and LLM-assisted skill scanners the rationale in-repo. - CI: bump
actions/checkout@v4to@v5in the release workflow; the v4 pin runs on the deprecated Node 20 runner.
v1.2.2
Clear the Snyk E005 "suspicious download URL" audit finding on scripts/publish-s3.sh, and harden the returned link.
- No hardcoded S3 endpoint in the shipped script. The two hand-built virtual-hosted S3 object URLs - which the skills.sh Snyk audit flagged as a generic "personal file hosting" download URL (rule E005) - are gone. The open URL is now derived from the AWS CLI's own presigner: the presigned URL is generated first and the clean object URL is that URL minus its query string. Behaviour is unchanged (public / static-website buckets still get the clean direct URL; private buckets still get the presigned one), but no literal S3 host string lives in the skill source for a scanner to match.
- Two latent bugs fixed for free. The old no-
HUMAN_HTML_S3_REGIONbranch built the legacy global endpoint (breaks on newer regions) and left${key}un-encoded; the presigner-derived URL is region-correct and URL-encoded by the SDK.
v1.2.1
Fix a pre-existing horizontal overflow at phone width, and harden the generator against its cause.
- Three canonical examples (architecture, review, understanding) no longer overflow at 390px. They predate the v1.1.4 overflow discipline: a wide
<table>without the responsive reflow (architecture),.compare-before/.compare-aftergrid items withoutmin-width:0so a wide<pre>forced the track past the viewport (review), and a long unbroken path in inline<code>with no wrap (understanding). The overflow was present before the v1.2.0 dark-mode change, not introduced by it. - Generator hardened against the same class. Grid items (
.grid-cols-2/.grid-2/.grid-3children and the compare blocks) now carrymin-width:0so a wide child scrolls instead of pushing the page, and inlinecodegetsoverflow-wrap:anywhereso a long path or token breaks. Verified with torture content (over-long<pre>lines in a compare grid plus a 72-character nonbreaking token) at 390px in both themes: zero overflow, layout-audit silent.
v1.2.0
Dark mode: every artifact and the gallery index now ship a built-in light/dark toggle (fixed top-right, sun/moon).
- Follows the reader's OS by default. First open with no saved choice matches
prefers-color-scheme; the toggle overrides and persists per-site inlocalStorage. With JavaScript off the page still follows the OS via a CSS media query and the (non-functional) toggle button stays hidden, so nothing is a dead control. - Degrade-safe and print-safe. The base
:rootstays light and renders on any engine; the contrast-verified dark palette is emitted into two screen-scoped selectors (an explicit:root[data-theme="dark"]choice and the OS media query). Wrapping the dark tokens in@media screenkeeps print-to-PDF light for clean handoff. A blocking head script applies a saved theme before first paint (no flash) and uses no.innerHTML, so the no-JS content contract is untouched. - Dark palette. A cool, navy-biased set mirroring the light tokens one-for-one: elevation inverts (raised surfaces get lighter), the six severity triples are re-tuned for a dark ground, the code block gains a border so it does not melt into the page, and shadows drop their tint. All text pairings clear WCAG AA.
- Mermaid diagrams pick their palette at load from the resolved theme, so a diagram matches the page it opens in; a mid-session toggle re-themes diagrams on the next reload.
- Branding dark. A plain
:rootbrand override still styles light only; to brand dark, append a:root[data-theme="dark"]override.SKILL.mdandreferences/patterns.mdupdated: the scaffold is no longer described as light-only.
v1.1.4
Scaffold robustness: fixes so a shipped artifact cannot overflow horizontally (which fired the runtime layout-audit banner and clipped content on a phone).
.meta-ribbon spanwaswhite-space:nowrap, so a single long value (e.g. a wordyStatus) could not wrap and pushed past a 390px phone viewport. Nowwhite-space:normal; overflow-wrap:anywhere; short label/value pairs still sit together, only an over-long value wraps internally..barsmisused as a definition list no longer blows out the page..bars ddnow carriesmin-width:0; overflow-wrap:anywhereso a<dt>/<dd>prose list fed into the progress grid degrades instead of forcing amax-contentcolumn.- New
.deflistcomponent for the label + wrapping description pattern (risks, key terms, glossary rows, a chip plus a paragraph).references/diagram-types.mdandreferences/patterns.mdsteer authors to it and warn against.barsfor prose. - New
figure.diagram/.diagram-scroll/.diagram-srcscaffold styles for shipping a diagram as inline SVG (self-contained under Quick Look, email, offline) with the source in an adjacent<details>. - Scrub two
—that rendered a visible em dash in emitted output (the gallery lede and the review scaffold body).
Verified with a real headless-Chrome measurement at 390px and 1280px: the fixed scaffold has zero true overflow sources; reverting the fixes reproduces the 1491px blowout.
v1.1.3
Review fixes.
publish-s3.sh: refuse to overwrite an existing object at the same key unlessHUMAN_HTML_S3_OVERWRITE=1is set; stop the auth-failure retry hint from double-applyingHUMAN_HTML_S3_PREFIX.references/patterns.md: fix heading hierarchy and correct an accessibility note aboutrole="region"and table semantics.references/workflow-integrations.md: add the requiredconfidencefield to the Decision schema and example, sync a JSON-LD question with its rendered text, fix a dangling cross-reference.- Scrub remaining em dashes from the reference docs.
v1.1.2
Consistency release.
- All nine canonical examples now share one house design system, so the gallery reads as a single consistent set. Content, metadata, comparison visuals, and the prototype's interactive sandbox are preserved; only the presentation was re-based onto the current scaffold.
- Reconciled a number inconsistency in the architecture example.