Skip to content

htmlify 1.0.0 — skill family, validated core, Hardcopy identity - #2

Merged
zakelfassi merged 14 commits into
mainfrom
v1
Jun 11, 2026
Merged

htmlify 1.0.0 — skill family, validated core, Hardcopy identity#2
zakelfassi merged 14 commits into
mainfrom
v1

Conversation

@zakelfassi

Copy link
Copy Markdown
Owner

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

  • deckify joins the repo — the presentation-deck skill (speaker notes, run-of-show, guide/PDF mode), de-personalized from a local fork, with a canonical deck template contract.
  • 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).
  • Hardcopy — original design system (engineering-plate language: warm paper, ink hairlines, serif display, mono metadata, one signal-orange accent, carbon code wells, stamps, crop marks), applied to the local renderer, the annotation layer, all assets, the gallery, and the landing page.
  • The repo is its own demoexamples/ 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.
  • Code quality — 1,421-line index.js decomposed into src/ modules; strict tsc --noEmit over JSDoc types with zero relaxations; Biome; 35 tests incl. validator, CLI, and manifest-integrity suites.
  • Distribution — Claude Code plugin manifest + marketplace, Codex/Cursor/Windsurf/Aider recipes, Pi/OMP entry points preserved; release-please with npm provenance.

Breaking

Root SKILL.md and references/ moved into skills/htmlify/. Migration: re-install pointing at skills/htmlify (and skills/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_TOKEN secret).

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
@zakelfassi
zakelfassi merged commit 8c52be8 into main Jun 11, 2026
7 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/validate.js
function detectProfile(htmlText) {
const html = String(htmlText || '');
SLIDE_SECTION.lastIndex = 0;
if (SLIDE_SECTION.test(html) && NOTES_ASIDE.test(html)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread src/constants.js
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread src/validate.js
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread README.md
| **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"` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread src/validate.js
Comment on lines +180 to +181
if (!KEYDOWN_LISTENER.test(html)) {
errors.push({ code: 'no-keyboard-nav', message: 'Deck must register a keydown listener for slide navigation.' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread src/constants.js
/<\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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant