htmlify 1.0.0 — skill family, validated core, Hardcopy identity - #2
Conversation
Pins pnpm 10 via corepack, adds biome (format + lint), a tsconfig for the upcoming checkJs pass, and lint/lint:fix/typecheck scripts. Applies the one-time biome formatting pass across the runtime.
Pure code move: constants, text, markdown, document, validate, annotation, comments, and artifacts modules plus an extension/ package for the Pi/OMP factory. index.js becomes a thin facade that preserves the extension factory export and every _internals name. The extension version label now derives from package.json instead of a hand-maintained date stamp.
Enables checkJs under strict tsc with zero compiler relaxations: shared typedefs in src/extension/types.js (PiHost, ExtensionCtx, ArtifactMeta, SourceRecord, ExportMeta, CommentBundle), annotations across src, bin, hooks, and tests, and an Object.assign export on index.js that keeps the extension factory and _internals contract intact.
Refactors the rich-HTML validator into issue collectors with stable codes, keeping validateRichHtmlDocument byte-compatible. New profiles: app (inline scripts allowed for interactive artifacts; external scripts, event handlers, and remote links still banned) and deck (app rules plus the deckify contract: slides, keyboard navigation, speaker notes on substantive slides, print CSS, size limits). htmlify-answer gains --validate/--profile/--format/--quiet with exit codes 0/1/2 and JSON output for agent consumption.
The repo becomes a skill family: skills/htmlify (documents) and skills/deckify (presentation decks with speaker notes, run-of-show, and guide mode), each self-contained with its own references. The Hardcopy visual spec ships identically in both skills. The deckify skill is adapted from a local fork, de-personalized, and given a canonical deck template contract that the deck validation profile enforces. Both SKILL.md files now require validating output via htmlify-answer --validate. npm files now ship src/, skills/, and the plugin manifest. BREAKING CHANGE: the root SKILL.md and references/ are removed. Clones that installed the repo root as a skill directory must re-install by pointing at skills/htmlify (and optionally skills/deckify). The htmlify-answer CLI, hook path, and Pi/OMP entry points are unchanged.
Introduces Hardcopy — the project design system (engineering-plate language: warm paper, ink hairlines, serif display, mono metadata, one signal-orange accent, carbon code wells, stamps, crop marks) — specified in references/hardcopy.md inside both skills. The local document renderer and the trusted annotation layer are reskinned to it, including dark mode and print rules. Repo assets are replaced with a new logomark, hero, skill-family, how-it-works, and social-preview set.
.claude-plugin/plugin.json + marketplace.json make the repo installable
via /plugin marketplace add zakelfassi/htmlify; both skills are
auto-discovered from skills/. hooks/hooks.json documents the opt-in Stop
hook with ${CLAUDE_PLUGIN_ROOT}. A manifest integrity suite keeps the
plugin version synced to package.json, skill frontmatter valid,
reference links resolvable, the two hardcopy.md copies identical, and
every bin/hook require inside the published file set.
Nine committed artifacts, all generated by the skills about this repository: seven htmlify documents (operator brief of the v1 launch, review packet for the real PR #1, incident report for the capture-notification bug, the monorepo decision brief, a runtime implementation map with an inline-SVG module graph, the HTML-vs-markdown explainer, and an interactive launch triage board) plus two deckify decks (the launch talk and a skill-authoring workshop with guide mode). Every file passes htmlify-answer --validate with its profile; CI enforces this.
index.html is itself an htmlify artifact — plate header, install tabs per agent, the nine-artifact gallery, skill-family split, principles, light/dark toggle — one file, inline CSS+JS, zero external assets. The Pages workflow copies index.html, examples/, and assets/ into the site artifact (a copy, not a build) and deploys via actions/deploy-pages.
Replaces the single test job with four: biome lint, tsc typecheck, a test matrix (Node 20/22/24 on ubuntu plus Node 22 on macOS for the darwin open-command path), and a job that runs every committed gallery artifact through htmlify-answer --validate.
Conventional commits on main drive an auto-maintained release PR, CHANGELOG, version bump (synced into the plugin manifest and both SKILL.md frontmatter blocks via release-please markers), a GitHub release, and an npm publish with provenance on release creation.
Repositions the project around the htmlify+deckify skill family and the live gallery: badges, install matrix per agent, validation profiles, artifact-mode table with gallery deep links, a compressed Pi/OMP runtime section, and a 0.x migration guide.
Final metadata sweep: skill-family description, gallery homepage, and deckify/claude-code/presentation keywords. Release-As: 1.0.0
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11d0e6b556
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function detectProfile(htmlText) { | ||
| const html = String(htmlText || ''); | ||
| SLIDE_SECTION.lastIndex = 0; | ||
| if (SLIDE_SECTION.test(html) && NOTES_ASIDE.test(html)) { |
There was a problem hiding this comment.
Do not require notes to classify decks
When --profile auto validates a deck that is missing speaker notes, this condition returns app because NOTES_ASIDE is absent, so collectDeckIssues never runs and the required deck checks are skipped. I verified test/fixtures/deck-no-notes.html is detected as app with no app-profile errors while the deck profile reports missing-notes; auto detection should treat documents with slide sections as decks so broken decks fail validation.
Useful? React with 👍 / 👎.
| const EXTERNAL_CSS_URL = /(?:url\(\s*(['"]?)\s*(?:https?:)?\/\/|@import\s+(?:url\(\s*)?(['"]?)\s*(?:https?:)?\/\/)/i; | ||
| // Interactive (app/deck) profiles allow inline scripts and form controls but still ban embeds. | ||
| const BLOCKED_EMBED_TAGS = /<\s*\/?\s*(?:iframe|object|embed|base)\b/i; | ||
| const SCRIPT_SRC_ATTR = /<\s*script\b[^>]*\ssrc\s*=/i; |
There was a problem hiding this comment.
Block SVG script hrefs in app profiles
In the app/deck profiles this only rejects <script src>, but SVG script elements can reference external code with href/xlink:href, so an artifact like <svg><script href="https://example.com/payload.js"></script></svg> currently passes validation with zero errors while still loading remote script. Extend the external-script check to cover script href attributes as well.
Useful? React with 👍 / 👎.
| errors.push({ code: 'external-script', message: 'Scripts must be inline; <script src> is not allowed.' }); | ||
| } | ||
| for (const link of html.match(LINK_TAG) || []) { | ||
| const href = /\shref\s*=\s*(['"]?)([^'">\s]+)\1/i.exec(link); |
There was a problem hiding this comment.
Trim link hrefs before allowing app artifacts
When an app/deck artifact contains a quoted <link> whose href starts with whitespace, this regex does not capture the attribute at all, so <link rel="stylesheet" href=" https://example.com/x.css"> passes validation even though browsers trim the value and fetch the external stylesheet. Normalize/trim the parsed href before the data: check so the external-asset ban cannot be bypassed with leading whitespace.
Useful? React with 👍 / 👎.
| | **Claude Code** | `/plugin marketplace add zakelfassi/htmlify` then `/plugin install htmlify@htmlify` | | ||
| | **Codex** | `git clone https://github.com/zakelfassi/htmlify.git ~/.htmlify && ln -sfn ~/.htmlify/skills/htmlify ~/.codex/skills/htmlify && ln -sfn ~/.htmlify/skills/deckify ~/.codex/skills/deckify` | | ||
| | **Cursor / Windsurf** | Clone the repo, point a project rule at `skills/htmlify/SKILL.md` / `skills/deckify/SKILL.md` | | ||
| | **Aider / anything** | `printf '%s' "$ANSWER" \| npx -y @zakelfassi/htmlify htmlify-answer --title "Review"` | |
There was a problem hiding this comment.
Fix npx examples to omit the bin argument
The documented npx -y @zakelfassi/htmlify htmlify-answer ... form passes htmlify-answer as the first argument to the package's only binary, so users hit Unknown argument: htmlify-answer instead of exporting or validating; I verified the same failure with the local package via npx -y . htmlify-answer --help. Use the package invocation directly, or the npm exec --package ... -- htmlify-answer form when explicitly selecting the bin.
Useful? React with 👍 / 👎.
| if (!KEYDOWN_LISTENER.test(html)) { | ||
| errors.push({ code: 'no-keyboard-nav', message: 'Deck must register a keydown listener for slide navigation.' }); |
There was a problem hiding this comment.
Ignore comments when checking deck keyboard nav
This whole-document search can be satisfied by inert text, so a deck with no navigation script but a comment like <!-- document.addEventListener("keydown", ...) --> validates without no-keyboard-nav; I verified such a two-slide deck returns no errors. Strip comments/non-executable content before applying this check, otherwise the deck validator can approve presentations that cannot advance with the keyboard.
Useful? React with 👍 / 👎.
| /<\s*\/?\s*(?:script|iframe|object|embed|link|base|form|input|button|textarea|select|option)\b/i; | ||
| const BLOCKED_META_REFRESH = /<\s*meta\b[^>]*http-equiv\s*=\s*(['"]?)refresh\1/i; | ||
| const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=/i; | ||
| const JAVASCRIPT_URL_ATTR = /\s(?:href|src|xlink:href|action|formaction)\s*=\s*(['"]?)\s*javascript:/i; |
There was a problem hiding this comment.
Reject external form submissions in app profiles
Because app/deck profiles allow form controls but this check only rejects javascript: actions, an artifact with <form action="https://example.com/collect"> currently passes validation with zero errors and can send user-entered data off the supposedly self-contained artifact. Treat action/formaction pointing at http(s) or protocol-relative URLs like other external network references.
Useful? React with 👍 / 👎.
What
Transforms the repo into a launch-grade 1.0: a monorepo skill family (
skills/htmlify+skills/deckify) sharing one zero-dependency runtime, a validation CLI with rich/app/deck profiles, the new Hardcopy visual identity, a nine-artifact dogfooded gallery + GitHub Pages landing page, full governance, CI matrix, and release-please automation.Highlights
htmlify-answer --validate FILE... --profile rich|app|deck|auto— the shared core both skills are required to run; exit codes 0/1/2, JSON output, deck contract checks (slides, keyboard nav, speaker notes).examples/holds nine artifacts generated by the skills about this repo (real PR Harden HTML export extension #1, a real bug, the real architecture decision…), all CI-validated.index.jsdecomposed intosrc/modules; stricttsc --noEmitover JSDoc types with zero relaxations; Biome; 35 tests incl. validator, CLI, and manifest-integrity suites.Breaking
Root
SKILL.mdandreferences/moved intoskills/htmlify/. Migration: re-install pointing atskills/htmlify(andskills/deckify). CLI flags, hook path, Pi/OMP entry points, commands, and env vars are all unchanged.Verification
pnpm lint && pnpm typecheck && pnpm test(35/35) green at tip; every gallery artifact passes--validate; document theme, decks, board, and landing page screenshot-verified in light/dark.Merging this triggers release-please to open the 1.0.0 release PR (npm publish requires the
NPM_TOKENsecret).