fix: address QA report P0-P2 issues for 10/10 agent experience (#208)

## Summary

Addresses all 8 issues from the QA report to improve agent and user experience.

### P0 — Must Fix

- **Blank template broken captions**: Removed `compositions/captions.html` and its reference from the blank template. Every agent (10/10) hit 404 errors during render.
- **Inner-wrapper example**: Added a clear structural comment in the blank template showing the correct `class="clip"` + inner wrapper pattern.
- **Sub-composition introspection**: `hyperframes compositions` now reads external HTML files referenced via `data-composition-src` and shows their real duration/element count instead of `0.0s / 0 elements`.

### P1 — Fix Soon

- **Font mapping warnings**: Now lists all mapped fonts, suggests alternatives (use a mapped font, add @font-face, install locally), and links to docs.
- **Browser 404s**: Non-font "Failed to load resource" 404s now prefixed with `[non-blocking]` instead of `[Browser:ERROR]`.
- **Render concurrency**: Default workers increased from `cores/2` (max 4) to `cores*3/4` (max 6). Added `--concurrency` alias.

### P2 — Nice to Have

- **Transform conflict fix suggestion**: `gsap_css_transform_conflict` now suggests exact GSAP property replacements (e.g., `xPercent: -50, yPercent: -50`).
- **Upgrade --yes**: Now actually runs the install instead of just printing the command.

## Before / After

### Font mapping warning

**Before:**

```
[Compiler] No deterministic font mapping for: DM Sans
```

**After:**

```
[Compiler] No deterministic font mapping for: DM Sans
  Mapped fonts: arial → inter, courier → jetbrains-mono, ...
  To fix, pick one:
    1. Use a mapped font name instead (see list above)
    2. Add a @font-face block in your HTML with a local or hosted font file
    3. Install the font locally on the render machine (Docker: add to Dockerfile)
    4. Add an alias to FONT_ALIASES in deterministicFonts.ts (for contributors)
```

### Browser 404s during render

**Before:** `[Browser:ERROR] Failed to load resource: the server responded with a status of 404`
**After:** `[non-blocking] Failed to load resource: the server responded with a status of 404`

### Transform conflict lint

**Before:** `Fix: Remove the transform from CSS and use tl.fromTo...`
**After:** `Fix: Remove transform: translate(-50%, -50%) from CSS and replace with GSAP properties: xPercent: -50, yPercent: -50`

### Compositions command

**Before:**

```
overlay   0.0s   1920×1080   0 elements
```

**After:**

```
overlay   8.0s   1920×1080   2 elements ← compositions/overlay.html
```

## Test plan

- [x] All 422 core tests pass
- [x] TypeScript compiles cleanly (all 4 packages)
- [x] Full monorepo build succeeds
- [x] `hyperframes init --template blank` ships without broken captions reference
This commit is contained in:
Miguel Ángel
2026-04-06 18:56:39 +02:00
committed by GitHub
parent c47e710ffc
commit baa3d813be
8 changed files with 198 additions and 136 deletions
+8 -8
View File
@@ -7,7 +7,7 @@ export const examples: Example[] = [
["Render transparent WebM overlay", "hyperframes render --format webm --output overlay.webm"],
["High quality at 60fps", "hyperframes render --fps 60 --quality high --output hd.mp4"],
["Deterministic render via Docker", "hyperframes render --docker --output deterministic.mp4"],
["Parallel rendering with 4 workers", "hyperframes render --workers 4 --output fast.mp4"],
["Parallel rendering with 6 workers", "hyperframes render --workers 6 --output fast.mp4"],
];
import { cpus, freemem } from "node:os";
import { resolve, dirname, join } from "node:path";
@@ -28,9 +28,9 @@ const VALID_FORMAT = new Set(["mp4", "webm"]);
const CPU_CORE_COUNT = cpus().length;
/** Half of CPU cores, capped at 4. Each worker spawns a Chrome process (~256 MB). */
/** 3/4 of CPU cores, capped at 8. Each worker spawns a Chrome process (~256 MB). */
function defaultWorkerCount(): number {
return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT / 2), 4));
return Math.max(1, Math.min(Math.floor((CPU_CORE_COUNT * 3) / 4), 8));
}
export default defineCommand({
@@ -66,8 +66,8 @@ export default defineCommand({
workers: {
type: "string",
description:
"Parallel render workers (1-8 or 'auto'). Default: half your CPU cores, max 4. " +
"Each worker launches a separate Chrome process.",
"Parallel render workers (number or 'auto'). Default: auto. " +
"Each worker launches a separate Chrome process (~256 MB RAM).",
},
docker: {
type: "boolean",
@@ -123,8 +123,8 @@ export default defineCommand({
let workers: number | undefined;
if (args.workers != null && args.workers !== "auto") {
const parsed = parseInt(args.workers, 10);
if (isNaN(parsed) || parsed < 1 || parsed > 8) {
errorBox("Invalid workers", `Got "${args.workers}". Must be 1-8 or "auto".`);
if (isNaN(parsed) || parsed < 1) {
errorBox("Invalid workers", `Got "${args.workers}". Must be a positive number or "auto".`);
process.exit(1);
}
workers = parsed;
@@ -155,7 +155,7 @@ export default defineCommand({
const workerLabel =
args.workers != null
? `${workerCount} workers`
: `${workerCount} workers (auto \u2014 half of ${CPU_CORE_COUNT} cores)`;
: `${workerCount} workers (auto ${CPU_CORE_COUNT} cores detected)`;
console.log("");
console.log(
c.accent("\u25C6") +