Skip to content

feat: add init tailwind flag#577

Merged
miguel-heygen merged 6 commits intomainfrom
feat/init-tailwind
Apr 30, 2026
Merged

feat: add init tailwind flag#577
miguel-heygen merged 6 commits intomainfrom
feat/init-tailwind

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen commented Apr 30, 2026

Problem

Users who want Tailwind utilities in a plain HyperFrames composition currently have to know which Tailwind browser script to add and where to place it. The first pass added --tailwind, but review caught three production-facing gaps: the CDN version was major-only, the insertion helper could silently no-op on compact HTML, and the render pipeline did not explicitly wait for Tailwind's async browser compilation before capturing frame 0.

There is also a version-specific agent risk: HyperFrames init --tailwind uses Tailwind v4.2 through @tailwindcss/browser@4.2.4, while packages/studio still uses Tailwind v3. Without a dedicated skill, agents can easily mix v3 tailwind.config.js / @tailwind patterns into v4 browser-runtime composition HTML.

What this fixes

  • Adds hyperframes init --tailwind.
  • Pins the Tailwind browser runtime to @tailwindcss/browser@4.2.4/dist/index.global.js with SRI and crossorigin="anonymous".
  • Injects a window.__tailwindReady promise next to the browser runtime.
  • Makes frame capture wait for window.__tailwindReady in both screenshot and BeginFrame capture modes before capturing frame 0.
  • Inserts Tailwind support before </head> case-insensitively, including single-line/minified heads, and falls back to prepending when there is no head tag.
  • Skips recursive Tailwind injection under .git, dist, and node_modules.
  • Tracks whether init used Tailwind in the existing init_template telemetry event.
  • Adds a first-party /tailwind skill for Tailwind v4.2 browser-runtime HyperFrames composition work.
  • Updates README, docs, generated project agent files, CLI skill guidance, and plugin metadata so the Tailwind skill is discoverable.
  • Documents the browser-runtime tradeoff and production/offline guidance.

Root cause

scaffoldProject() copied the selected example and patched media placeholders, then immediately wrote project metadata and package.json. There was no optional post-copy step for framework-specific HTML support. The initial Tailwind post-copy step also treated the browser runtime like a static script, but Tailwind compiles utilities asynchronously after scanning the DOM, so the capture engine needed an explicit readiness contract.

On the agent side, the repo exposed HyperFrames, CLI, GSAP, registry, and runtime adapter skills, but had no Tailwind-specific instruction to separate the v4 browser-runtime composition path from Studio's v3 internal setup.

Verification

Local checks

  • bunx vitest run packages/cli/src/commands/init.test.ts
  • bun run --filter @hyperframes/cli test src/commands/init.test.ts
  • bun run --filter @hyperframes/cli typecheck
  • bun run --filter @hyperframes/engine typecheck
  • bun run lint:skills
  • bun run lint
  • npx skills add . --list showed 12 local skills, including tailwind.
  • bunx oxfmt --check packages/cli/src/commands/init.ts packages/cli/src/commands/init.test.ts packages/cli/src/telemetry/events.ts packages/engine/src/services/frameCapture.ts docs/packages/cli.mdx
  • bunx oxfmt --check README.md docs/quickstart.mdx docs/packages/cli.mdx CLAUDE.md packages/cli/src/templates/_shared/CLAUDE.md packages/cli/src/templates/_shared/AGENTS.md skills/hyperframes-cli/SKILL.md skills/tailwind/SKILL.md .codex-plugin/plugin.json .cursor-plugin/plugin.json
  • bunx oxlint packages/cli/src/commands/init.ts packages/cli/src/commands/init.test.ts packages/cli/src/telemetry/events.ts packages/engine/src/services/frameCapture.ts
  • git diff --check
  • Lefthook pre-commit: lint/format/typecheck for code commit; format for docs/skill commit
  • Lefthook commit-msg: commitlint

Generated-project render proof at /tmp/hf-tailwind-render-proof:

  • bun packages/cli/src/cli.ts init /tmp/hf-tailwind-render-proof --example blank --tailwind --non-interactive --skip-skills
  • Added a temporary Tailwind-only card using flex, h-full, w-full, items-center, justify-center, bg-slate-950, rounded-3xl, bg-white, px-20, py-12, text-8xl, font-black, text-black, and shadow-2xl.
  • bun packages/cli/src/cli.ts lint /tmp/hf-tailwind-render-proof → 0 errors, 0 warnings.
  • bun packages/cli/src/cli.ts validate /tmp/hf-tailwind-render-proof → 0 errors, 0 regular warnings; the temp proof still reports validator contrast warnings even though the rendered/browser pixels show black text on white background.
  • bun packages/cli/src/cli.ts render /tmp/hf-tailwind-render-proof --workers 1 --fps 24 --quality draft --output /tmp/hf-tailwind-render-proof-artifacts/output.mp4
  • Render compiler inlined both GSAP and https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4/dist/index.global.js.
  • ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate,duration -of default=noprint_wrappers=1 /tmp/hf-tailwind-render-proof-artifacts/output.mp4 → H.264, 1920x1080, 24fps, 10s.
  • Extracted frame-0 proof: /tmp/hf-tailwind-render-proof-artifacts/frame-000.png.

Browser verification

  • Started Studio preview for /tmp/hf-tailwind-render-proof.
  • Used agent-browser to open http://localhost:5194.
  • Verified the Tailwind-styled composition rendered in Studio preview.
  • Captured screenshot: /tmp/hf-tailwind-render-proof-artifacts/browser/tailwind-preview.png.
  • Captured agent-browser-driven recording: /tmp/hf-tailwind-render-proof-artifacts/browser/tailwind-preview.webm.
  • Served the PR worktree locally and used agent-browser to open the new Tailwind skill proof page.
  • Verified the browser-visible skill content includes @tailwindcss/browser@4.2.4.
  • Captured screenshot: /Users/miguel07code/.codex/worktrees/pr-577-tailwind-comments/tmp/agent-browser-proof/tailwind-skill.png.
  • Captured agent-browser-driven recording: /Users/miguel07code/.codex/worktrees/pr-577-tailwind-comments/tmp/agent-browser-proof/tailwind-skill.webm.

Notes

  • This still intentionally uses Tailwind's browser runtime rather than adding a generated Tailwind build pipeline. That keeps hyperframes init --tailwind small and compatible with the current no-install generated project workflow.
  • The /tailwind skill cites official Tailwind v4 docs plus community skill references, but its instructions are HyperFrames-specific and tuned for the pinned v4.2 browser runtime.
  • Browser proof artifacts are local-only under /tmp/hf-tailwind-render-proof-artifacts/ and tmp/agent-browser-proof/ and intentionally not committed.

@mintlify
Copy link
Copy Markdown

mintlify Bot commented Apr 30, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview Apr 30, 2026, 6:51 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment thread packages/cli/src/commands/init.ts Fixed
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

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

Staff-engineer review. Small, well-scoped DX add — implementation is clean and the test bar is reasonable for the scaffolding side. The bulk of my comments are on the runtime-rendering implications of shipping @tailwindcss/browser@4 into the render pipeline, which the PR description doesn't address.

Things to fix before merge

1. Pin the Tailwind CDN version (determinism)

packages/cli/src/commands/init.ts:58

const TAILWIND_BROWSER_SRC = "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4";

This pins to the major, so jsdelivr returns whatever 4.x.y is current at fetch time. Two renders of the same composition months apart can produce different output frames as Tailwind ships JIT/preflight changes. For a tool whose value prop is "HTML → reproducible video," that's a quiet trap.

Pin to a specific minor at minimum (@4.1.x), and ideally add SRI:

<script
  src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.1.13/dist/index.global.js"
  integrity="sha384-..."
  crossorigin="anonymous"
></script>

The constant should live next to a comment explaining why the version is pinned, so the next person doesn't bump it carelessly.

2. Render-time correctness: Tailwind's browser runtime is async

@tailwindcss/browser scans the DOM, compiles utilities, and injects a <style> tag after load. The capture engine has no synchronization with that pass. For long-running fixtures and the warm-grain example this likely hides — but for compositions whose first useful frame is at t=0 (title cards, fast cuts), the renderer can capture pre-compile, unstyled frames.

The PR description says the proof composition rendered correctly. That's one fixture with a static title. I'd want one of:

  • A producer-side baseline test that renders a Tailwind-using composition and verifies frame 0 already has utilities applied (i.e. compositor waited), OR
  • An explicit await window.__tailwindReady (or equivalent) hook the runtime pipeline can poll, OR
  • A clear note in docs/packages/cli.mdx that Tailwind is "best-effort at the start of the timeline; insert a 100–200 ms intro buffer if you see unstyled first frames."

This is the single most important issue. Without it, --tailwind will eventually produce a non-reproducible "first frame is broken" bug report and we won't know where it came from.

3. The injection regex has fragile fallbacks

packages/cli/src/commands/init.ts:232-241

if (/<script[\s>]/i.test(html)) {
  return html.replace(/(\n[ \t]*)(<script)([\s>])/i, `$1${script}$1$2$3`);
}
return html.replace(/(\n[ \t]*)<\/head>/i, `$1${script}$1</head>`);

Both branches require a \n followed by whitespace before the anchor. Any template that gets minified, gets a single-line <head>...</head>, or has a <script> immediately after > with no whitespace will silently no-op — --tailwind reports success, the user gets no Tailwind, and the failure mode is "my classes aren't applying."

Two cheap fixes:

  • Drop the leading-newline requirement: /([ \t]*)(<script)/i and prepend the script with \n instead, OR
  • Just always inject before </head> (case-insensitive) and stop trying to "preserve script ordering" — if Tailwind needs to come before user <script>s anyway, the </head> position is always correct (and matches v4 docs).

The current "before first script tag" branch is also the more dangerous one — it places Tailwind ahead of <script src="…gsap.min.js"> in the bundled blank template, which means GSAP load is now serialized behind Tailwind's compile. For renders where you're driving cold-start time down, that's a measurable regression. Recommend: insert just before </head> with defer, or after the existing <script> rather than before it.

4. Bundled-but-unrelated refactors

Two changes in this PR shouldn't be part of "feat: add init tailwind flag":

  • .github/workflows/preview-regression.yml — swapping apt for FedericoCarboni/setup-ffmpeg. Worth doing (and the SHA pin is correct), but it's a CI tweak, separate blast radius.
  • buildPackageScripts() extraction at init.ts:195-204 — pure cosmetic cleanup; the new code path doesn't touch package.json.

Not blocking, but next time these belong in their own commits or PRs so a revert of the Tailwind feature doesn't drag CI infra with it.

Things worth a comment but not blocking

  • No interactive prompt for --tailwind. The wizard mode (--human-friendly) silently can't reach this feature. Add a confirm step, or accept that it's a flag-only opt-in and call that out in cli.mdx.
  • listHtmlFiles walks recursively after copy. Today templates are flat, but the moment someone ships an example with a node_modules-like dir or a dist/ of HTML fragments, Tailwind gets injected into all of them. Consider scoping to top-level *.html or excluding node_modules/dist.
  • No trackInitTailwind telemetry. trackInitTemplate(templateId) already runs; emitting whether --tailwind was set would let you measure adoption. Cheap to add now.
  • Test coverage gap. The --tailwind enables Tailwind utilities test only asserts the script tag is in the file. It doesn't assert idempotency (running with --tailwind against a destDir that already had Tailwind), and doesn't exercise the </head>-only branch. Both are one-liners against injectTailwindBrowserScript (already exported for testing).
  • Scaffold with Tailwind CSS help example at init.ts:9--tailwind alone falls back to --example blank in non-interactive mode, but in interactive mode it'll prompt. Worth showing it as hyperframes init my-video --example blank --tailwind so the example is copy-pasteable in both modes.

What's good

  • Test for uppercase <SCRIPT> shows you thought about real-world template variance — right instinct.
  • injectTailwindBrowserScript exported and pure — clean testability.
  • Idempotent guard (if (html.includes(TAILWIND_BROWSER_SRC)) return html) is correct.
  • Docs updated alongside code.
  • TDD red→green note in the PR description is the right hygiene.

Summary

Approve once (1) version pin and (3) regex robustness are addressed. Item (2) render synchronization is the real long-term concern — if you don't want to handle it in this PR, please file a follow-up issue ("Tailwind browser runtime: synchronize style application with first frame capture") and link it in the docs note so the trap is at least visible.

@miguel-heygen
Copy link
Copy Markdown
Collaborator Author

@jrusso1020 thanks for the detailed review. I addressed the blocking Tailwind runtime concerns and updated the PR description with the new proof.

What changed:

  • Pinned the Tailwind browser runtime to @tailwindcss/browser@4.2.4/dist/index.global.js with SRI + crossorigin="anonymous".
  • Replaced the fragile newline/script-tag injection path with a case-insensitive </head> insertion path that also handles single-line/minified heads, with a fallback when no head exists.
  • Added a generated window.__tailwindReady promise and made frame capture wait for it before frame 0 in both screenshot and BeginFrame modes.
  • Added tests for exact CDN/SRI output, idempotency, single-line head insertion, uppercase head handling, and ensuring the readiness shim does not introduce render-loop APIs like Date.now, setTimeout, or requestAnimationFrame.
  • Tightened recursive HTML walking to skip .git, dist, and node_modules.
  • Added Tailwind usage to the existing init telemetry event and documented the browser-runtime tradeoff.

Verification added to the PR body:

  • focused unit/type/lint/format checks
  • fresh /tmp/hf-tailwind-render-proof project
  • lint, validate, and real render to MP4
  • ffprobe on the rendered video
  • extracted frame-0 proof showing Tailwind styles applied
  • agent-browser Studio screenshot + recording

Latest replacement CI run is green for the core checks, CLI smoke/global install, Windows tests/render, docs, preview regression, and CodeQL. Some broader regression shards are still pending.

Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

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

Re-review on da37d5ef. All three "fix before merge" items resolved; render-synchronization was solved properly rather than papered over with a docs note. Approving — remaining comments are nits and one small maintainability suggestion.

How my blocking items were resolved

1. Pin version + SRI ✅

packages/cli/src/commands/init.ts:58-63

const TAILWIND_BROWSER_VERSION = "4.2.4";
const TAILWIND_BROWSER_SRC = `https://cdn.jsdelivr.net/npm/@tailwindcss/browser@${TAILWIND_BROWSER_VERSION}/dist/index.global.js`;
const TAILWIND_BROWSER_INTEGRITY = "sha384-v5YF9xS+gLRWdvrQ0u/WRbCkjSIH0NjHIPe8tBL1ZRrmI7PiSH6LLdzs0aAIMCuh";

Exact pin, SRI hash, crossorigin="anonymous", comment explaining why. Version split into its own constant so a future bump is a one-token diff. Good.

2. Render-time correctness — solved properly ✅

This is the part I want to call out. Instead of taking the docs-note escape hatch I offered, you built a real synchronization primitive:

  • The CLI injects an inline shim before the Tailwind CDN script that exposes window.__tailwindReady as a Promise.
  • The Promise resolves when both (a) document.readyState === "complete" and (b) a <style> tag containing tailwindcss v is observed in the DOM.
  • Detection uses MutationObserver only — no Date.now, setTimeout, or requestAnimationFrame. That's deliberate (and unit-tested), and it matters because BeginFrame mode virtualizes the page's clock; using rAF or wall-time timers in the page can deadlock against the renderer driving the event loop.
  • Engine-side, frameCapture.ts:263-281 adds waitForOptionalTailwindReady, gated on the presence of the global. Wired into both warmup (:370) and BeginFrame (:464) paths, after document.fonts?.ready.
  • Failure mode is a loud, actionable error rather than silently-unstyled frame 0:
    [FrameCapture] window.__tailwindReady not resolved after ${timeoutMs}ms.
    Tailwind browser runtime must finish before frame capture starts.
    

This is the kind of fix I'd expect from somebody who actually understood the constraint, not just patched around the comment. Nice work.

3. Regex robustness ✅

init.ts:268-273

if (/<\/head>/i.test(html)) {
  return html.replace(/<\/head>/i, (closingHead) => `\n${script}\n${closingHead}`);
}
return `${script}\n${html}`;

One case-insensitive anchor, no leading-newline assumption, single-line head test added. Fallback prepends to the document instead of silently no-op'ing — also good.

4. (Non-blocking) The unrelated CI / buildPackageScripts refactors stayed

That's fine — I called it non-blocking originally.

Other addressed items

  • listHtmlFiles now skips .git, dist, node_modules (init.ts:231-249)
  • Telemetry: trackInitTemplate(templateId, { tailwind }) in both code paths
  • Help example: hyperframes init my-video --example blank --tailwind (copy-pasteable in both modes)
  • New tests: exact CDN+SRI script string, idempotency, single-line head, uppercase </HEAD>, and the absence-of-render-loop-APIs test

Remaining nits (none blocking)

a. The tailwindcss v string marker is undocumented coupling

init.ts:289

if (text.indexOf("tailwindcss v") !== -1) return text;

This is reading the comment header Tailwind's browser runtime emits in the injected <style>. If a future version drops or rephrases that header, the readiness Promise never resolves and renders fail at pageReadyTimeout with a misleading "Tailwind didn't finish" error.

The version pin protects you (any bump requires explicit re-verification), but a one-line comment in the shim noting the coupling would help the next person:

// Detects Tailwind's "/*! tailwindcss v… */" header in the injected <style>.
// If the version constant above changes, re-verify this marker still matches.

b. Consider extracting the inline shim to its own file

The shim is a 12-line ES5 string concatenated inside init.ts:254-267. oxfmt could re-flow that array of string fragments and subtly change the emitted JS. Moving it to packages/cli/src/templates/_shared/tailwind-ready.shim.js and reading it at scaffold time would:

  • Get syntax highlighting and lint coverage on the shim itself
  • Decouple shim formatting from the surrounding TS file
  • Make the shim diffable when Tailwind's marker changes

Optional — not worth a re-review round.

c. Late-resolution leak in waitForOptionalTailwindReady

frameCapture.ts:266-274

const ready = await Promise.race([
  page.evaluate(`Promise.resolve(window.__tailwindReady).then(() => true, () => false)`),
  new Promise<boolean>((resolve) => setTimeout(() => resolve(false), timeoutMs)),
]);

Promise.race doesn't cancel the loser. If the timeout wins, the inner page.evaluate is still running on the page and may emit an unhandled-rejection warning if the page closes before it resolves. Cheap fix: stash the inner Promise, attach a no-op .catch(() => {}) to it after the race. Optional.

d. Producer-side integration test (follow-up)

The CLI side now exhaustively tests the shim string and the injection logic. There's no producer-side baseline that actually renders a Tailwind composition through frameCapture and asserts frame 0 has utilities applied. Worth a follow-up issue to add one when the producer baseline harness has time available — that's the only thing standing between us and a real regression guard.

Summary

Approving. The blocking items are all resolved, and the __tailwindReady design is the right one. The four nits above are all post-merge follow-ups at most.

@miguel-heygen
Copy link
Copy Markdown
Collaborator Author

@jrusso1020 @vanceingalls shipped the Tailwind skill follow-up in cbd85e6.

What changed:

  • added first-party /tailwind skill for HyperFrames init --tailwind projects
  • pinned the guidance to the actual runtime this PR uses: @tailwindcss/browser@4.2.4
  • called out the v4 browser-runtime vs Studio v3 split so agents do not use tailwind.config.js / @tailwind directive patterns in scaffolded composition HTML
  • wired the skill into README, docs, generated AGENTS/CLAUDE guidance, hyperframes-cli skill guidance, and plugin metadata
  • credited official Tailwind v4 docs and reviewed community Tailwind v4 skills without vendoring their text

Extra validation:

  • bun run lint:skills → 12 skills, no issues
  • npx skills add . --list → lists tailwind
  • bun run --filter @hyperframes/cli test src/commands/init.test.ts
  • bun run lint
  • agent-browser proof loaded the skill content and verified @tailwindcss/browser@4.2.4 is visible; screenshot/recording paths are in the PR body.

CI is running on the new head now.

Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

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

Re-review on cbd85e6c (docs: add tailwind composition skill). Pure documentation + skill addition — no code or test changes from da37d5ef. The earlier approval still stands; posting this as informational follow-up.

What changed

  • New skills/tailwind/SKILL.md — 150-line skill teaching agents Tailwind v4 browser-runtime patterns specific to HyperFrames compositions
  • Cross-references in repo CLAUDE.md, README.md, docs/quickstart.mdx, docs/packages/cli.mdx
  • Scaffolded-project docs (packages/cli/src/templates/_shared/{AGENTS,CLAUDE}.md) advertise /tailwind alongside the existing skill table
  • skills/hyperframes-cli/SKILL.md adds the --tailwind example and a "invoke /tailwind first" pointer
  • Plugin manifests (.codex-plugin/plugin.json, .cursor-plugin/plugin.json) get "tailwind" in keywords and the description

Skill quality

A few things worth calling out as deliberately well-handled:

  • Version contract is explicit. Pinned to @tailwindcss/browser@4.2.4, and the skill warns against substituting cdn.tailwindcss.com (which is v3). Matches the code pin in init.ts.
  • The __tailwindReady invariant is documented as a constraint, not a footnote. "The readiness shim must stay deterministic: no render-loop polling APIs, no clock-based retries…" — exactly the design intent from the harden commit, now codified for future agents who might be tempted to bolt setTimeout retries on.
  • Dynamic class warning ("Tailwind's browser runtime scans the current document and generates CSS for class names it can see. Do not build render-critical class names only at seek time") is a real gotcha for animated compositions and the right thing to flag.
  • v4 vs v3 nuance is correct: directs users away from @tailwind base/components/utilities and tailwind.config.js, but acknowledges @config for the compiled-CSS path. Doesn't oversimplify.
  • Studio carve-out — the skill is explicit that it's only for scaffolded projects, not for packages/studio which runs Tailwind v3 internally. Good — prevents agents from "fixing" Studio.

Nits (none blocking)

  • The two community-skill reference URLs at the bottom (skills.sh/..., agent-skills.md/...) are third-party links that will eventually 404. Either drop the links and keep attribution prose, or accept that they'll rot.
  • Worth a quick sanity check that npx skills add heygen-com/hyperframes actually registers /tailwind as a bare slash command (vs. namespaced). Since /gsap, /animejs, etc. presumably already work that way, you're likely fine.
  • The example @utility text-balance-tight { text-wrap: balance; ... } is illustrative; real compositions in the skill use the built-in v4 text-balance utility. Slight inconsistency but not confusing.

No further action needed from me.

Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

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

LGTM — solid implementation of the --tailwind scaffold + a well-disambiguated v3-vs-v4 skill.

Mechanics:

  • Pinned to exact @tailwindcss/browser@4.2.4/dist/index.global.js. SRI hash matches the published artifact (re-computed locally and confirmed). crossorigin="anonymous" set.
  • __tailwindReady Promise shim is deterministic — uses MutationObserver watching for the Tailwind-generated <style> block containing tailwindcss v to appear. No setTimeout, no Date.now, no requestAnimationFrame. The "keeps readiness shim free of render-loop APIs" test enforces this string-level contract; if someone reverts to a timing-API-based shim, it fails.
  • frameCapture.ts:waitForOptionalTailwindReady wired into both BeginFrame and Screenshot mode after document.fonts?.ready. Soft no-op when Tailwind isn't present (so non-tailwind projects aren't penalized); hard error if __tailwindReady exists but doesn't resolve within pageReadyTimeout. Right shape.
  • listHtmlFiles rewritten to walk explicitly + skip .git, dist, node_modules instead of readdirSync({ recursive: true }). Important fix — the recursive variant would have descended into node_modules and corrupted vendored HTML.
  • Idempotency check (html.includes(TAILWIND_BROWSER_SRC)) prevents double-injection on re-runs. Insert-before-</head> works for both single-line and multi-line head tags + uppercase variants (test coverage explicit on each).

Symmetric proof: reverted only init.ts while keeping the new tests → 5 of 7 init tests fail red. Restoring → 192/192 cli tests pass. New tests genuinely cover the new behavior.

Skill content (skills/tailwind/SKILL.md):

  • Pinned to @tailwindcss/browser@4.2.4 consistent with the CLI.
  • Explicitly calls out v4-vs-v3 confusion: 13 in-context v3 references, all prescribing AGAINST v3 patterns (no @tailwind base/components/utilities, no tailwind.config.js, use @theme/@utility in <style type="text/tailwindcss"> instead).
  • Important note that packages/studio still uses Tailwind v3 internally — agents shouldn't attempt a v3→v4 migration there based on this skill.
  • Calls out v4 utility renames (shadow-xs, rounded-xs, outline-hidden, shrink-*, grow-*) and the v4 default-border behavior change (border alone may not work; use border border-white/20). Real gotchas that agents will hit.
  • Dynamic-class-safety section is correct — the browser runtime scans current DOM, so element.className = \bg-${color}-500`` may not be detected before capture. Solid agent-UX.
  • Frontmatter follows the same shape as existing skills. Plugin manifest descriptions + keywords updated; CLAUDE.md template includes the new skill in its table.

Two non-blocking nits:

  1. Plugin sync follow-up: .codex-plugin/plugin.json description and keywords got updated to mention Tailwind. After merge, this needs a sync PR to openai/plugins/plugins/hyperframes/ per the manual-mirror gotcha I documented earlier (.cursor-plugin/.claude-plugin consume the hyperframes repo directly so they auto-pick-up; openai/plugins doesn't).

  2. Two external community references in "Credits And References": skills.sh/tlq5l/tailwindcss-v4-skill and agent-skills.md/skills/bout3fiddy/agents/tailwindcss. Linking them as references is fine, but agents reading the skill might fetch them as authoritative sources during composition work. Consider whether the team wants agents pulling third-party skill content into a build pipeline (or move these to a "## Inspired by" footnote without URLs that look fetch-friendly).

The CI sweep-along (apt-get install ffmpegFedericoCarboni/setup-ffmpeg@v3 action, SHA-pinned) is a clean unrelated-but-correct change.

— Review by Rames Jusso

@miguel-heygen
Copy link
Copy Markdown
Collaborator Author

@jrusso1020 thanks, agreed on the maintenance nits. I pushed d8843fb as a tiny docs-only cleanup:

  • removed the brittle third-party community-skill URLs while keeping attribution prose
  • renamed the sample custom utility from text-balance-tight to headline-balance so it does not look like a variant of built-in text-balance
  • re-ran bun run lint:skills, bunx oxfmt --check skills/tailwind/SKILL.md, and npx skills add . --list — still shows 12 skills including bare tailwind

On the npx skills add heygen-com/hyperframes sanity check: the live repo install path cannot show /tailwind until this PR lands, but local skills discovery already lists the skill as bare tailwind, same shape as gsap and the adapter skills.

@miguel-heygen miguel-heygen merged commit 68bd52a into main Apr 30, 2026
38 checks passed
Copy link
Copy Markdown
Collaborator Author

Merge activity

@miguel-heygen miguel-heygen deleted the feat/init-tailwind branch April 30, 2026 22:07
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.

3 participants