Commit Graph
188 Commits
Author SHA1 Message Date
Vance IngallsandClaude Opus 4.6 614a67dfd3 fix: caption init race condition, auto-save, and panel auto-open
- Remove store reset from useEffect cleanup — was wiping state on dep changes
  and breaking auto-save subscription
- Move panel auto-open into useEffect instead of during render
- Add fallback 500ms poll alongside postMessage listener for initial load

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:13 -07:00
Vance IngallsandClaude Opus 4.6 f2718783c2 fix: use runtime postMessage events to detect caption composition loading
Replace polling with a message listener for hf-preview state/timeline
events. These fire after loadExternalCompositions completes, guaranteeing
.caption-group elements exist in the DOM. Eliminates the race condition
on initial page load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:13 -07:00
Vance IngallsandClaude Opus 4.6 96866cddf6 feat: auto-detect captions from any composition, edit from master view
Caption edit mode now activates automatically when .caption-group elements
are found in the iframe DOM. Works from index.html (master) without
requiring drill-down into the captions sub-composition. Finds the captions
source file via data-composition-src attribute.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:12 -07:00
Vance IngallsandClaude Opus 4.6 76a667c281 feat: auto-activate caption editor when viewing captions composition
- Caption overlay and hover states always active on captions compositions
- Clicking a word auto-opens the property panel
- Remove Edit Captions button — edit mode activates automatically
- Reset edit mode when navigating away from captions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:12 -07:00
Vance IngallsandClaude Opus 4.6 0e1148556f feat: arrow key nudge for selected caption words
1px per press, 10px with Shift held. Syncs to store and auto-saves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:12 -07:00
Vance IngallsandClaude Opus 4.6 f321269a65 refactor: strip property panel to position, scale, rotation only
Remove typography, color, opacity, stroke, and background controls.
Keep only Position (X/Y) and Transform (Scale/Rotation) — the controls
that work reliably with the wrapper-based override system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:12 -07:00
Vance IngallsandClaude Opus 4.6 9dd394eb70 feat: active/dim color states for caption word overrides
Replace single "color" with activeColor (spoken) and dimColor (inactive).
Runtime hook modifies existing GSAP color tweens by detecting dim vs active
states via opacity heuristic. Property panel shows both color pickers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:12 -07:00
Vance IngallsandClaude Opus 4.6 613ed774ae refactor: remove preview refresh suppression during caption edit mode
No longer needed — caption overrides are a JSON data file, not HTML patches.
The runtime hook re-applies overrides on every reload, so preview refresh
during editing is safe and ensures the preview stays in sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:12 -07:00
Vance IngallsandClaude Opus 4.6 933b72b633 feat: auto-save caption overrides on model change
Replace manual Save button with 800ms debounced auto-save. Writing to
caption-overrides.json is safe because preview refresh is suppressed
during caption edit mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:12 -07:00
Vance IngallsandClaude Opus 4.6 b7d7a1c2b0 fix: wrapper-aware element resolution for caption overlay
- getWordEl skips wrapper spans and finds actual word elements inside them
- readWordBoxes handles wrapped words in position calculations
- getOrCreateWrapper detects if element IS a wrapper to prevent double-wrapping
- Remove debug logging from save

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:11 -07:00
Vance IngallsandClaude Opus 4.6 bf27578755 fix: caption overrides runtime hook — run in both load paths, write to project root
- applyCaptionOverrides() now runs both when compositions load async AND when
  already ready (standalone composition view)
- Overrides file written to project root (not compositions/) to match <base href>
- Remove debug logging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:11 -07:00
Vance IngallsandClaude Opus 4.6 a60c9e523b feat(lint): enforce .caption-group > span convention for caption overrides
- New lint rule `caption_word_structure`: warns when caption compositions don't
  use <span> word elements inside .caption-group containers
- Document required DOM structure in the captions skill
- Document word ID field in transcript format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:11 -07:00
Vance IngallsandClaude Opus 4.6 b6bdce065e feat: wrapper-based caption overrides — preserve all GSAP animations
Instead of killing transform tweens, wrap each overridden word span in an
inline-block wrapper span. Editor transforms (position, scale, rotation)
are applied to the wrapper while the inner span keeps all GSAP entrance,
exit, and karaoke animations intact.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:11 -07:00
Vance IngallsandClaude Opus 4.6 7c0f825d72 feat: caption overrides with stable word IDs and killTweensOf
- Add optional `id` field to transcript Word interface, assigned as `w{index}` during normalization
- caption-overrides.json uses `wordId` (preferred) with `wordIndex` fallback
- Runtime hook calls gsap.killTweensOf for overridden transform properties before applying
- Studio parser carries wordId through from transcript to segments
- Add typecheck to lefthook pre-commit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:11 -07:00
Vance IngallsandClaude Opus 4.6 5d2efb24c1 feat: replace HTML patching with caption-overrides.json data file
- New caption-overrides.json format: [{wordIndex, x, y, scale, rotation, color, ...}]
- Runtime hook in packages/core applies overrides via gsap.set() after compositions load
- Studio writes/reads caption-overrides.json instead of injecting code into HTML
- Original composition source is never modified
- Remove old parseExistingOverrides and source patching code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:11 -07:00
Vance IngallsandClaude Opus 4.6 b00f062bdf chore: revert unrelated LintModal changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:10 -07:00
Vance IngallsandClaude Opus 4.6 7c07d09cf3 chore: add typecheck to lefthook pre-commit and fix container type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:10 -07:00
Vance IngallsandClaude Opus 4.6 cb335ba2a7 feat(studio): caption designer canvas controls, real-time editing, and save-as-patch
- Canvas: move (translate), scale, and rotate handles on selected words via gsap.set()
- Property panel: real-time style updates applied directly to iframe DOM
- Save: patches original source with gsap.killTweensOf + gsap.set per edited word
- Parser: reads existing editor overrides on re-entry to preserve prior edits
- Suppress preview refresh during caption edit mode to prevent reload cycle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:10 -07:00
Vance Ingalls 9f73dae176 feat(studio): add caption module barrel export 2026-04-01 23:37:10 -07:00
Vance IngallsandClaude Sonnet 4.6 2e850cb360 feat(studio): add caption timeline track with word-level blocks and drag-to-retime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 23:37:10 -07:00
Vance Ingalls 876a053b90 feat(studio): add caption animation panel with entrance/highlight/exit controls 2026-04-01 23:37:10 -07:00
Vance Ingalls 0233ae2361 feat(studio): wire caption property panel and parser integration 2026-04-01 23:37:10 -07:00
Vance IngallsandClaude Sonnet 4.6 97b88d86fc feat(studio): add caption property panel with typography, color, and transform controls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 23:37:09 -07:00
Vance IngallsandClaude Opus 4.6 9370e4fb7b feat(studio): wire caption overlay and edit mode into Studio
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:09 -07:00
Vance Ingalls fce9268304 feat(studio): add drag-to-reposition for caption segments 2026-04-01 23:37:09 -07:00
Vance Ingalls 9128524cf4 feat(studio): add caption overlay with segment selection boxes 2026-04-01 23:37:09 -07:00
Vance Ingalls 486a018dc9 feat(studio): add caption model → HTML source sync hook 2026-04-01 23:37:09 -07:00
Vance IngallsandClaude Sonnet 4.6 980ca1f750 feat(studio): add caption HTML generator
Implements generateCaptionHtml(model) which serializes a CaptionModel
into a complete captions.html HyperFrames composition with template
wrapper, CSS per-group classes, TRANSCRIPT array, and GSAP timeline
registration via window.__timelines["captions"].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 23:37:09 -07:00
Vance Ingalls 39712ba6e7 feat(studio): add caption editing Zustand store 2026-04-01 23:37:09 -07:00
Vance Ingalls be97bfadd6 feat(studio): parse caption styles from live iframe DOM 2026-04-01 23:37:08 -07:00
Vance IngallsandClaude Sonnet 4.6 7b5cfea5e8 feat(studio): build caption model from transcript words
Adds `buildCaptionModel` to the caption parser — groups TranscriptWord
arrays into CaptionGroups (default 5 words each), creates CaptionSegments
with correct timing and groupIndex, and wires in DEFAULT_STYLE,
DEFAULT_CONTAINER, and DEFAULT_ANIMATION_SET. Covers 23 tests (13 new).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 23:37:08 -07:00
Vance IngallsandClaude Sonnet 4.6 012d1afbf3 feat(studio): add transcript extraction from caption source
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 23:37:08 -07:00
Vance Ingalls 0d310fc05d feat(studio): add caption model types 2026-04-01 23:37:08 -07:00
Vance IngallsandClaude Opus 4.6 68821df356 fix(cli): handle unknown type in pageerror handler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:08 -07:00
Vance IngallsandClaude Opus 4.6 aa9175adc1 fix(cli): fix typecheck errors in validate command
- ConsoleMessage.type() returns "warn" not "warning"
- Type pageerror callback parameter as Error

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:07 -07:00
Vance IngallsandClaude Opus 4.6 2ba35674a0 feat(cli): add hyperframes validate command
Loads a composition in headless Chrome and reports console errors,
uncaught exceptions, and failed network requests for project assets.
Agents run this before opening the studio to catch runtime issues.

- Bundle HTML with inlined runtime (no CDN dependency)
- Minimal file server for project assets
- Captures JS errors, pageerror events, 404s on project files
- JSON output mode for programmatic consumption
- ~3s execution time, suitable as a blocking pre-preview step

Updates CLAUDE.md and compose skill to require lint + validate
before opening the studio.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:07 -07:00
Vance IngallsandClaude Opus 4.6 949ff52177 fix(studio): serve runtime locally instead of from CDN
The Vite dev server was loading the HyperFrames runtime IIFE from
cdn.jsdelivr.net. This caused compositions using window.__hyperframes
(e.g., fitTextFontSize) to fail when the CDN script didn't load in
the iframe context.

Add a /api/runtime.js endpoint to the Vite plugin that serves the
local built runtime, matching what studioServer.ts already does for
the CLI embedded server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:37:07 -07:00
Vance IngallsandClaude Opus 4.6 41d1286c33 feat(lint): warn when captions use fetch() instead of inline TRANSCRIPT
Adds two lint rules for caption compositions:
- caption_transcript_not_inline: warns when transcript is loaded via
  fetch() instead of an inline var TRANSCRIPT array
- caption_transcript_parse_error: warns when the inline TRANSCRIPT
  array is not valid JSON (common with unquoted keys + apostrophes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 06:25:12 +00:00
Vance IngallsandClaude Opus 4.6 4e516327ed feat(whisper): language detection, speech onset, contraction merging
- Auto-detect language and switch from .en to multilingual model when needed
- Detect speech onset in WAV to strip hallucinated words before speech begins
- Merge whisper-cpp token fragments: contractions (didn + 't → didn't),
  split capitals (C + aught → Caught), dropped-g (shin + in' → shinin')
- Interpolate zero-duration word clusters for reliable karaoke timing
- Replace non-null assertions with optional chaining in tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 06:25:08 +00:00
James 870ca76c8b chore: release v0.2.1 2026-04-01 21:36:24 +00:00
Miguel Ángel 1efb23dfae fix(producer): inline CDN scripts for offline render and detect black video (#182)
## Context

`npx hyperframes render` fails to load assets and produces all-black video. Root causes:

1. CDN scripts (GSAP, Lottie) are left as `<script src="https://...">` in compiled HTML — the headless browser must fetch them over the network, which fails in Docker, CI, and firewalled environments
2. Assets referenced from outside the project directory (e.g. `../shared-assets/hero.png`) 404 because the file server only serves from `projectDir`
3. When GSAP fails to load, the timeline never registers, all `.clip` elements stay `visibility: hidden`, and every frame is black — with no diagnostic output
4. The linter reports `missing_gsap_script` when GSAP is bundled inline (no `<script src>` tag), which blocks users from working around the CDN issue

## What changed

### 1\. CDN script inlining (`htmlCompiler.ts`)

`compileForRender` now downloads all external `<script src="https://...">` tags at compile time and inlines their content into the HTML. Rendering no longer needs network access.

| Before | After |
| --- | --- |
| `<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>` stays in HTML, browser must fetch at runtime | Script downloaded during compilation, embedded as `<script>/* inlined: https://... */\n...code...</script>` |
| Docker/CI render → `net::ERR_NAME_NOT_RESOLVED` → black video | Render works fully offline |
| No feedback when CDN fails | `[Compiler] WARNING: Failed to download CDN script: ... Consider bundling it locally` |

### 2\. External asset copying (`htmlCompiler.ts`, `renderOrchestrator.ts`)

After compilation, the HTML is scanned for `src`, `href`, and CSS `url()` references that resolve outside `projectDir`. These files are copied into the compiled output directory so the file server can serve them.

| Before | After |
| --- | --- |
| `background-image: url(../shared-assets/hero.png)` → 404 (file server can't serve outside `projectDir`) | Asset detected, copied to compiled dir, path rewritten → serves correctly |
| `<img src="../shared-assets/logo.png">` → 404 | Same fix — works for all `src`/`href` attributes and CSS `url()` |

### 3\. Black video diagnostics (`renderOrchestrator.ts`)

When composition duration is 0 (which would produce a black video), the error now probes the browser for diagnostics instead of a generic message.

| Before | After |
| --- | --- |
| `Invalid composition duration: 0. Check that GSAP timelines are registered.` | `Composition duration is 0 — this would produce a black video.\n\nDiagnostics:\n  - GSAP is not loaded — CDN script may have failed to download. Bundle GSAP locally...\n  - Browser: [Browser:PAGEERROR] gsap is not defined` |
| Asset 404s during page load silently logged | `[Render] Asset load failure: ...` + `[WARN] Browser encountered network failures during page load` |

### 4\. Linter: recognize inline GSAP (`core/lint/rules/gsap.ts`)

The `missing_gsap_script` rule now recognizes GSAP bundled inline — matching the producer's inlining comment (`/* inlined: ...gsap... */`), GSAP library internals (`_gsScope`, `GreenSock`), and large inline scripts (>5KB) referencing gsap.

| Before | After |
| --- | --- |
| User inlines GSAP → linter errors with `missing_gsap_script` | Inline GSAP detected, no false error |
| Producer inlines CDN → linter errors on the compiled HTML | Producer's `/* inlined: ... */` comment recognized |

## Test plan

- [x] `pnpm build` passes
- [x] Core tests pass (410/410, +2 new)
- [x] **Reproduced baseline failures on** **`main`**: CDN scripts not inlined, external assets 404, no diagnostics
- [x] **Verified fixes**: CDN script inlined, external assets copied and served, diagnostics printed
- [x] Render with working CDN → `[Compiler] Inlined CDN script: ...` → render succeeds
- [x] Render with broken CDN → `[Compiler] WARNING: Failed to download CDN script` + browser errors surfaced
- [x] Render with assets outside project dir → `[Compiler] Found 1 asset(s) outside project directory` → assets served correctly
- [x] Linter with inline GSAP → no `missing_gsap_script` false positive
2026-04-01 23:15:00 +02:00
James RussoandClaude Opus 4.6 2d30654632 feat(cli): improve --help with grouped commands and per-command examples (#184)
* feat(cli): improve --help with grouped commands and per-command examples

Replaces citty's flat COMMANDS list with kubectl-style grouped categories
and adds examples to every subcommand. Root help now groups 14 commands
into 5 categories (Getting Started, Project, Tooling, AI & Integrations,
Settings). Per-command --help now appends a formatted Examples section
with practical usage patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(cli): use shared colors, lazy-load help, fix description drift

- Replace hand-rolled ANSI helpers with existing ui/colors.ts (fixes
  non-conformant NO_COLOR handling)
- Add cyan and gray to shared color module
- Lazy-load help.ts via dynamic import to avoid allocating help data
  on non-help invocations
- Fix description drift: benchmark and transcribe descriptions now
  match their command meta.description
- Unify tuple order: ROOT_EXAMPLES now uses [comment, command] to
  match COMMAND_EXAMPLES
- Remove redundant comment restating type annotation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): resolve typecheck error in showUsage generic signature

The lazy-load wrapper needs to cast CommandDef<T> to CommandDef when
forwarding to the help module, since TypeScript's generic variance
makes the direct assignment incompatible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(cli): remove kubectl references from help comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:55:26 -07:00
Miguel Ángel 2213e9f7a4 refactor(lint): split monolithic test file into per-rule-module test files (#181)
## Summary

- Splits the 872-line `hyperframeLinter.test.ts` into focused per-domain test files matching the rule module structure from #170
- Tests still use the public API (`lintHyperframeHtml`) — no internals exposed
- Fixes misplaced tests: `gsap_css_transform_conflict` tests were inside `describe("lintScriptUrls")`, caption/adapter tests were inside `describe("template_literal_selector rule")`

## New structure

```
src/lint/
  hyperframeLinter.test.ts   — orchestrator integration + lintScriptUrls (8 tests)
  rules/
    core.test.ts             — root attrs, timeline registry, host id (6 tests)
    media.test.ts            — duplicate ids, missing id/src, preload (6 tests)
    gsap.test.ts             — clip element, transform conflict, missing script (12 tests)
    captions.test.ts         — caption exit, overflow, relative position (5 tests)
    composition.test.ts      — external script, template literal selector (5 tests)
    adapters.test.ts         — missing Lottie/Three.js scripts (8 tests)
```

## Test plan

- [x] 404 tests pass (402 before — 2 extra from previously misplaced adapter tests now correctly counted)
- [x] Lint + format clean
2026-04-01 18:30:13 +02:00
Miguel Ángel 1681350ac4 fix(preview): rewrite sub-composition asset urls in styles (#174)
## Summary
- rewrite CSS `url(...)` asset paths from sub-compositions before styles are hoisted into bundled/master preview output
- rewrite standalone sub-composition preview HTML so `src`/`href` paths keep resolving correctly under the preview root `<base>`
- add regression tests for bundled CSS asset rewriting and standalone sub-composition preview rewriting

## Root cause
Standalone composition previews reused the project `<head>` with a preview-root `<base href="/api/projects/:id/preview/">`, but the sub-composition body still contained `../...` asset references. Those escaped the preview route and 404ed. Separately, bundled preview already rewrote `<img src="../...">` paths but left hoisted CSS asset references like `@font-face src: url("../font.woff2")` untouched.

## Validation
- `pnpm --filter @hyperframes/core exec vitest run src/compiler/htmlBundler.test.ts src/studio-api/helpers/subComposition.test.ts`
- `pnpm --filter @hyperframes/core exec tsc --noEmit`
- `pnpm --filter @hyperframes/producer exec tsc --noEmit` *(blocked by pre-existing `packages/producer/src/services/deterministicFonts.ts` importing missing generated file `./fontData.generated.js` in the worktree install)*
- browser verification with `agent-browser` against the local preview server using `/Users/miguel07code/dev/test-hyperframes/heygen-promo`

## Browser proof
Verified the fixed preview routes in-browser after seeking to visible frames:
- standalone composition preview
- bundled master preview
2026-04-01 18:13:38 +02:00
James 1a5badc803 chore: release v0.2.0 2026-04-01 03:28:05 +00:00
Miguel Ángel 110ea12597 fix(core,engine,producer): handle id-less media in sub-composition renders (#96)
## What

Move the id-less media fix into the shared timing compiler so producer can resolve durations for sub-composition videos before inlining, then carry the merged result through engine parsing, regression coverage, and the regression Docker image used in CI.

This PR now does five concrete things:

- assigns stable ids to id-less media in core `compileTimingAttrs()` so unresolved duration injection can target them
- keeps the engine-side `parseVideoElements()` support for `video[src]` plus the newer `data-duration` / natural-duration fallback from `main`
- makes producer prefer sub-composition media metadata over the later inlined-document parse when the same media id appears in both places
- makes `sub-composition-video` a runnable regression test by fixing its metadata and checking in the missing `output/compiled.html` snapshot
- removes the stale `pnpm-workspace.yaml` copy step from `Dockerfile.test`, so regression CI builds the Bun-based test image from the current workspace layout

## Why

- media without an explicit `id` could not participate in unresolved-duration resolution early enough
- producer could lose the resolved sub-composition timing by overwriting it with the later inlined parse
- the regression fixture intended to cover this case was not actually running in CI because its `meta.json` was incomplete and the required compiled snapshot was missing
- the regression image definition still expected a deleted `pnpm-workspace.yaml`, so GitHub Actions failed before the test shard could start

Putting the id-generation step in core makes the behavior reusable instead of relying on producer-only HTML patching.

## How

### Shared compiler

- core `compileTimingAttrs()` now auto-assigns stable ids to id-less `video` / `audio` tags
- those generated ids are returned in `unresolved`, so `injectDurations()` can add `data-duration` and `data-end` to the same media element later in the pipeline
- added core tests that cover auto-id assignment and duration injection for generated ids

### Producer

- when producer combines `subVideos` / `subAudios` with the media re-parsed from the final inlined HTML, it now lets the sub-composition metadata win
- this preserves the resolved/clamped timing already computed for nested media instead of overwriting it with the later parse
- `sub-composition-video` now has valid regression metadata and a checked-in `output/compiled.html` snapshot so CI actually executes it

### Engine

- resolved the merge conflict in `videoFrameExtractor` by keeping the broader `video[src]` parsing from this branch and the `data-duration` / natural-duration fallback that landed on `main`
- added a focused engine unit test for videos without ids

### CI image

- `Dockerfile.test` now copies only `package.json` and `bun.lock` at the workspace root before `bun install --frozen-lockfile`
- this matches the current monorepo layout and removes the obsolete pnpm-era dependency on `pnpm-workspace.yaml`

## Test plan

- [x] `bun run --filter @hyperframes/core test`
- [x] `bun run --filter @hyperframes/engine test`
- [x] `bun run --filter @hyperframes/producer test --update --sequential sub-composition-video`
- [x] `bun run --filter @hyperframes/producer test --sequential sub-composition-video`
- [x] Browser check with `agent-browser` against the compiled fixture page (`http://127.0.0.1:8123/compiled.html`)
- [x] Clean tracked-only Docker build of `Dockerfile.test` with the PR version of the file applied

## Notes

- Latest regression workflow is green on `main`, but before this PR the `sub-composition-video` fixture was being skipped by the harness rather than exercised end to end.
- The CI Docker fix was validated from a tracked-only export to avoid local untracked worktree artifacts affecting the result.
2026-04-01 02:59:22 +02:00
Vance Ingalls 1e4c101fb4 feat: lint for audio tag existingon found project audio (#169) 2026-03-31 16:16:56 -07:00
Vance Ingalls 0dcf73d62a feat: async skills install (#172)
## What

Added progress reporting to the skills installation process by converting synchronous operations to asynchronous ones and implementing progress callbacks.

## Why

The skills installation process can take a significant amount of time, especially when cloning repositories or running npm operations. Users need feedback about what's happening during the installation to understand progress and know the system hasn't frozen.

## How

- Converted `execFileSync` calls to a new `execFileAsync` function using promises
- Made all installation functions (`runSkillsAdd`, `gitClone`, `fetchRepo`, `fallbackInstall`) asynchronous
- Added an optional `onProgress` callback parameter to `installAllSkills` that accepts progress messages
- Integrated progress reporting in the `init` command by passing spinner message updates to the progress callback
- Added progress messages for key installation steps like "Installing {source} skills..." and "Cloning skill repositories..."
- Added "giget" to the external dependencies list in the build configuration

## Test plan

- [ ] Unit tests added/updated
- [ ] Manual testing performed
- [ ] Documentation updated (if applicable)
2026-03-31 16:06:44 -07:00
Miguel Ángel 5421c23fff refactor(lint): break 1,314-line monolith into focused rule modules with plugin system (#170)
## Summary

- Breaks `hyperframeLinter.ts` from 1,314 lines (one massive function) into a plugin system of focused rule modules
- Introduces `LintContext` — HTML is parsed once and shared across all rules
- Adds `LintRule<TContext>` type as the formal contract for rules
- Public API unchanged: `lintHyperframeHtml`, `lintMediaUrls`, `lintScriptUrls` signatures identical

## New file structure

```
src/lint/
  utils.ts          — shared types (OpenTag, ExtractedBlock), regex constants, helpers
  context.ts        — LintContext type + buildLintContext() factory
  rules/
    core.ts         — structural rules (root attrs, timeline registry, script syntax)
    media.ts        — media element rules (duplicate ids, video pitfalls, placeholder URLs, etc.)
    gsap.ts         — GSAP rules + GSAP-specific parsing utils
    captions.ts     — caption rules
    composition.ts  — timed element, deprecated attrs, template literal selector, external scripts
    adapters.ts     — Lottie + Three.js missing-script rules (from PR #149)
  hyperframeLinter.ts — orchestrator only (~200 lines, down from 1,314)
```

## Adding a new adapter rule going forward

1. Create `src/lint/rules/my-adapter.ts` exporting `myAdapterRules: LintRule[]`
2. Import and spread into `ALL_RULES` in `hyperframeLinter.ts`

## Test plan

- [x] All 402 core tests pass unchanged
- [x] Full workspace build clean (`pnpm build`)
- [x] TypeScript strict mode clean (`pnpm tsc --noEmit`)
2026-04-01 00:50:13 +02:00
Miguel Ángel 5f3488e996 feat(studio): drag-drop assetfile/folder and asset import anywhere in the studio (#155)
## Summary

- Add `/api/projects/:id/upload` endpoint for multipart file uploads with automatic dedup naming
- Wire `onImportFiles` from Assets tab "Import media" button through to the upload API
- Add global drag-drop overlay — drop media files **anywhere** in the studio, not just the Assets panel
- Files that already exist get `(2)`, `(3)` suffixes instead of overwriting
- Support uploading into subdirectories via `?dir=` param — dropping on a folder imports there
- Make folders draggable in the file tree + support drop-to-root
- Add `bodyLimit` middleware for early rejection of oversized payloads
- Surface skipped/failed uploads via toast notification instead of console-only

Addresses feedback: _"Wish I could upload/drag-drop assets directly in the Studio (music, images, video) like a CapCut media panel"_

## Test plan

- [x] Open studio, drag an image/video/audio file onto any part of the UI
- [x] Verify the drop overlay appears with "Drop files to import" message
- [x] Drop the file — verify it appears in the Assets tab and file tree
- [x] Drop a file with the same name — verify it gets a `(2)` suffix
- [x] Click "Import media" button in Assets tab — verify file picker works
- [x] Import multiple files at once via drag-drop
- [x] Drop a file onto a nested folder in the file tree — verify it lands in that folder
- [x] Drag a folder in the file tree and drop it on another folder or root — verify it moves
- [x] Drop a file >500MB — verify toast notification appears
- [x] Verify drag overlay doesn't get stuck when dragging over nested UI elements
2026-04-01 00:21:34 +02:00