Commit Graph
2928 Commits
Author SHA1 Message Date
James Russo 065f8c9c8d Revert "docs(changelog): weekly digest 2026-06-29–2026-07-06 (#1995)" (#2571)
This reverts commit 9dc7dcd7fb.
2026-07-16 17:34:47 -04:00
James RussoandClaude Opus 4.8 9dc7dcd7fb docs(changelog): weekly digest 2026-06-29–2026-07-06 (#1995)
Auto-generated weekly digest (bun run changelog:weekly), hand-cleaned to
publish quality. Grouped into Features / Fixes / Performance / Docs &
Examples with readable one-liners and commit + PR links.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:32:33 -04:00
James RussoandJake Moran e96ebd74de feat(skills): add changelog-video skill for repo-native CC + Codex discovery (#2552)
Packages Jake Moran's changelog-video pipeline (v1, validated end-to-end
by Home on the Jun 23-29 range) as a repo-native skill set that Claude
Code (.claude/skills/) and Codex CLI (.agents/skills/) auto-discover the
moment the repo is opened. No install step; run the skill against a
changelog markdown for a given git range and it produces a lint-clean,
seam-gate-green 1080x1080 MP4 (~45-60s, Annie VO, mock-UI visualizations,
caption rail) end-to-end.

Six skills added byte-identical in both mirror dirs:
- changelog-video (pipeline entry point)
- motion-doctrine (carries seam-stamp.mjs + seam-gate.mjs)
- cut-the-curve, captions-overlay, seam-craft, oversized-cursor

Layout:
- .claude/skills/  - Claude Code project-local auto-discover
- .agents/skills/  - Codex CLI project-local auto-discover (verified via
                     Magi's clean-home Codex 0.144.3 repro; NOT .codex/skills/)

Fonts, animated background (12 MB), house BGM (5 MB), lexicon, and
align-captions ship inside the skill dirs. .gitattributes routes only
.claude/skills/**/*.{mp4,mp3} + .agents/skills/**/*.{mp4,mp3} through
LFS — narrowly scoped so unrelated Player, Studio, registry, and
marketplace media stay put. HeyGen CLI auth is the one credential the
skill needs; Node >= 22, ffmpeg, and headless Chrome are documented
alongside in both READMEs.

.gitignore: rewrites .claude/ and .agents/ blocks to keep agent-installed
skill hygiene while re-including the six repo-native skill dirs plus
README.md.

CI:
- Extends changes.skills filter to match .claude/skills/**,
  .agents/skills/**, scripts/lint-skills.ts, and scripts/check-skill-mirror.mjs.
- New 'Skills: project-native lint + mirror' job runs the extended
  lint-skills.ts (schema-driven; required { name, description } + optional
  { license, allowed-tools, metadata }, name pattern check, description
  length check) plus a new check-skill-mirror.mjs byte-integrity script
  (24 mirrored files must match; README.md deliberately per-CLI).
- Wired into 'bun run lint' locally.

Frontmatter validator:
- Rejects unsupported top-level keys (catches category:-style drift).
- Requires name + description.
- Validates name pattern (^[a-z][a-z0-9-]{0,63}$) and description shape
  (non-empty, <=1024 chars).
- Missing frontmatter block itself is a first-class error.

Also strips unsupported top-level 'category:' frontmatter from Jake's
motion-doctrine and cut-the-curve SKILL.mds (both mirrors), rewrites the
TTS invocation from ~/.claude/skills/media-use/... to the tracked
skills/hyperframes-media/scripts/heygen-tts.mjs, swaps npx hyperframes@latest
for the repo-local CLI in the gate step, and fixes a lint issue in Jake's
seam-gate.mjs (ternary-for-side-effect -> if/else).

Validated end-to-end by Home on Jun 23-29 (MP4 posted in C0ACCNHLG3U
thread 1784181166.041319). Independently reviewed R1/R2/R3 by Magi.

Co-authored-by: Jake Moran <jake@heygen.com>
2026-07-16 17:29:19 -04:00
James Russo 172f2757f0 feat(studio): clarify storyboard review handoff (#2560)
## What changed

- adds a stage-aware review guide for plan, sketch, animation, and final-review states
- makes the review loop explicit: review frames → save feedback → reply in agent chat
- adds copyable agent handoff and approval messages
- keeps frame status agent-owned and presents planned frames before previews exist
- clarifies voiceover ownership, save state, feedback errors, and preview availability
- improves responsive layout and surfaces storyboard warnings without dominating the board
- adds unit coverage for review-stage and handoff-step derivation

## Why

Storyboard reviewers could leave comments, but the UI did not clearly explain when a pass was ready, what to inspect, or how saved feedback reaches the agent. This makes the human-to-agent iteration loop visible while preserving the existing file-based handoff.

## Impact

Users get a clear next action throughout the storyboard lifecycle and can copy the exact message needed to continue the agent workflow. The underlying storyboard and frame-comment formats are unchanged.

## Validation

- `bun run --filter @hyperframes/studio test --run src/components/storyboard/storyboardReviewStage.test.ts` — 13 tests passed
- `bun run --filter @hyperframes/studio typecheck`
- pre-commit: tracked artifacts, lint, formatting, Fallow audit, typecheck
2026-07-16 16:16:42 -04:00
James 68beec0e76 fix(studio): guard storyboard history navigation 2026-07-16 15:49:40 -04:00
James ded443647d fix(studio): address storyboard review feedback 2026-07-16 14:56:11 -04:00
James 73dd38f93e feat(studio): clarify storyboard review handoff 2026-07-16 14:56:11 -04:00
James Russo 4b9135393e Merge pull request #2159 from heygen-com/07-10-refactor_engine_add_browser_leases
refactor(engine): add fingerprinted browser leases
2026-07-16 14:42:06 -04:00
James d4cfa08cb6 fix(engine): escalate hung browser lease closes 2026-07-16 12:58:10 -04:00
James 9b23c00237 refactor(engine): add fingerprinted browser leases 2026-07-16 12:58:10 -04:00
WaterrrForever 3bb26b0f08 docs(skills): make the core set the default install on every surface (#2554)
* docs(skills): make the core set the default install on every surface

A field test showed an agent with a real 'make videos' intent installing
all 19 skills: at install time, every surface it could read pointed at the
full set, while the core-eager / workflow-on-demand design only exists
inside hyperframes/SKILL.md - unreadable until after the install decision.

Two traps made full-install the documented default:
- The README Quick Start used 'skills add --yes': skills.sh force-detects
  agent environments into non-interactive mode, and a non-interactive run
  without --skill installs all 19. Dropping --yes fixes the human path
  (the picker opens with nothing pre-selected), but not the agent path.
- marketplace.json listed the full 'hyperframes' bundle first, under the
  name an agent installing 'hyperframes' matches; core-skills sat second.

Changes, each behavior verified by an isolated run:
- README Quick Start drops --yes (humans get the picker; verified via a
  pty capture that nothing is pre-selected) and points agents and
  non-interactive runs at 'npx hyperframes skills update', which from a
  clean HOME installs exactly the 8 core skills, refreshes stale ones and
  prunes unpublished ones on an existing machine, and is idempotent.
- CLAUDE.md and the docs install guide lead with the same one-liner;
  --all is reworded to explicit-request-only at every surface.
- marketplace.json puts core-skills first and both descriptions steer the
  default choice; the full entry keeps auto-discovery (no allowlist),
  per the skillsManifest core-pin test (56/56 pass).

* docs(skills): close the same install trap in AGENTS.md

Review follow-up on the core-default change: AGENTS.md still carried a
bare 'npx skills add heygen-com/hyperframes' - no --full-depth, and the
same non-interactive-installs-everything trap the README fix closed.
AGENTS.md is the first file Codex/Cursor-family agents read for repo
intent, so it leaked the full-set default to exactly the readers the
core-default policy targets. It now leads with the same core-set
one-liner and policy line as CLAUDE.md.
2026-07-17 00:49:43 +08:00
Miguel Ángel 771145a5c4 fix(registry): remove invalid media from caption components (#2454)
* fix(registry): remove invalid media from caption components

* test(registry): enforce nested media contracts
2026-07-16 12:16:45 -04:00
Miguel Ángel 55ee559e40 fix(lint): catch cold-seek opacity reveals (#2503)
* fix(lint): catch cold-seek opacity reveals

* fix(lint): resolve hidden selector aliases

* style(lint): format gsap rule
2026-07-16 12:03:25 -04:00
Miguel Ángel 9b17a5ad7e fix(cli): avoid Python whisper CLI collision (#2414) 2026-07-16 12:03:21 -04:00
Miguel Ángel ed1f38124b fix(engine): preserve mono audio level (#2392) 2026-07-16 12:03:17 -04:00
Miguel Ángel e846cd6004 fix(cli): fail clearly on unsupported Node versions (#2388) 2026-07-16 12:03:13 -04:00
Miguel Ángel 584be6d64a fix(transcribe): select multilingual model before download (#2303) 2026-07-16 12:03:09 -04:00
Miguel Ángel 75eedf5cc1 fix(cli): time out stalled model downloads (#2415) 2026-07-16 11:45:49 -04:00
Miguel Ángel 335e7483b5 fix(talking-head): preserve source audio (#2260) 2026-07-16 11:30:49 -04:00
Miguel Ángel b8a44e49c9 fix(lint): ignore !important in font families (#2538) 2026-07-16 10:43:17 -04:00
WaterrrForever f8c33cab72 feat(skills): act on stale CLI pin during project resume (#2540)
* feat(skills): probe and bump stale CLI pins during project resume

The entry skill now keeps a resumed project's pinned CLI current instead of
leaving that to a notice nobody acts on. On resuming a project with pinned
scripts, run the read-only probe 'npx hyperframes@latest upgrade --project
. --check'; when it (or the stale-pin stderr notice, or _meta.updateAvailable
from a pinned run) reports the project behind, apply the bump and verify
with 'hyperframes check'. A failed check reverts the bump and keeps the
project on its pinned version, preserving the reproducibility contract the
pin exists for.

The probe matters because the stale-pin notice only exists in >= 0.7.59:
a pinned run of an older CLI prints no warning at all, so a notice-only
trigger never fires for exactly the projects most behind. The probe runs
unpinned, so its behavior never depends on the project's CLI age.

Telemetry: the fleet converges to new releases within about a week via the
background auto-updater and ephemeral npx, but pinned projects form a
persistent stale tail (~10% of weekly actives, e.g. 6.3k users still on
0.6.x three weeks after 0.7.0).

Both skill surfaces now pass an explicit dir ('--project .') because a bare
'--project' followed by another flag consumes that flag as its directory
value and no-ops; the parsing fix is a separate CLI change.

* fix(cli): stop bare --project from eating the next flag as its directory

citty parses --project as a string option, so 'upgrade --project --check'
arrived with project="--check": the dir resolved to a nonexistent path and
the command no-opd with 'No package.json found' while --check was lost.
The documented default-cwd behavior only worked when --project was the
final token — and the trap-prone form is exactly what the scaffolded
template CLAUDE.md instructs.

A leading dash can never be a real directory argument, so resolveProjectArgs
now reclaims the eaten token as the flag the user wrote (--check / --json),
falls back to the current directory, and drops unrelated eaten flags rather
than treating them as paths. Templates and skill references switch to the
explicit-dir form ('--project .'), which behaves correctly on every release
including ones that predate this fix.

* feat(skills): report a successful pin bump in the run summary

Review follow-up on the stale-pin rule: 'hyperframes check' validates
composition structure, not render-output equivalence, so a check-passing
bump can still shift a project's rendered output. The bump stays the right
default for stale projects, but it must not be silent — the summary now
names the old and new version so the user knows the reproducibility
trade was made.
2026-07-16 22:28:11 +08:00
James Russo 1b4cf12cd3 refactor(cli): add stderr diagnostics logger + oxlint guard (#2551) 2026-07-16 09:35:27 -04:00
James Russo d92d1d4f51 fix(cli): keep diagnostics on stderr for --json commands (#2522) 2026-07-16 09:14:39 -04:00
Somansh ReddyandClaude Opus 4.8 5e78a29af3 docs(guides): add Claude Design → Send to HyperFrames single-file import guide (#2131)
* docs(guides): add Claude Design → Send to HyperFrames single-file import guide

Self-contained authoring guide for the 'Send to HyperFrames' MCP import path
(distinct from the download-ZIP flow): single self-contained HTML wire format,
the composition contract the importer validates, inline-brand-assets fidelity
rules (base64 data: URIs), no bundled/splash artifacts, keep-the-original-as-
reference, a complete worked skeleton, the animation-pattern catalog, and
determinism rules. Reviewed for technical accuracy against a known-good
imported+rendered composition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(send-to guide): assets must be resolvable (inline OR fetchable URL), not inline-only

Large media can exceed the import size cap, so "inline everything" is wrong for it,
and the import tool description now states "resolvable refs." Align the guide: inline
data: URIs preferred (fonts/images/logos — the only fully self-contained form), a
publicly-fetchable absolute URL allowed for large media, and explicitly forbid relative
paths / local-file variables / expiring-or-private hosts (the media-expiry trap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 04:43:57 -07:00
Vance Ingalls dbb684fa6c Merge pull request #2535 from heygen-com/fix/runtime-partial-timeline-pause-guard
fix(runtime): tolerate registry timelines without pause() in interactive transport
2026-07-16 03:03:22 -07:00
Vance Ingalls 74f52fcd96 Merge pull request #2534 from heygen-com/fix/panel-sections-audio-layout-style-gate
fix(studio): gate Layout and Style panel sections on the element having a rendered box
2026-07-16 02:43:57 -07:00
Vance IngallsandClaude Fable 5 7e0bd401c8 chore(core): regenerate position-edits-render artifact with current esbuild
The committed artifact was generated with an older esbuild than the
current lockfile resolves; CI's check:position-edits-render regen now
produces different (equivalent) minifier variable naming and fails the
diff gate. Regenerate to match — no source change to positionEdits.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 02:17:36 -07:00
Vance Ingalls eb967f1542 Merge pull request #2533 from heygen-com/fix/studio-promotable-badge-overlap
fix(studio): reserve a gutter for the promote-to-variable badge
2026-07-16 02:06:28 -07:00
Vance Ingalls 1a2e7524e2 Merge pull request #2532 from heygen-com/fix/studio-flat-stroke-select
fix(studio): make flat inspector's Stroke width free of style-name typing
2026-07-16 02:06:07 -07:00
Vance Ingalls f96b78165e Merge pull request #2528 from heygen-com/fix/studio-flat-row-values-right-aligned
fix(studio): right-align FlatRow's value input in the flat inspector
2026-07-16 02:05:48 -07:00
James Russo 6a1585df27 refactor(render): centralize execution ownership (#2158) 2026-07-16 05:00:40 -04:00
Vance IngallsandClaude Fable 5 8826d2d71d fix(runtime): tolerate registry timelines without pause() in interactive transport
A window.__timelines entry is authored content and may be a partial
RuntimeTimelineLike (duration/seek only, no pause). Timeline resolution
is deliberately permissive — duration-based — and such compositions
render fine, because the render path only seeks. But every interactive
transport path (play/pause/seek, bind, rebind-tick, boot) called
capturedTimeline.pause() unguarded, crashing studio playback with
'tl.pause is not a function' — the top recurring studio:unhandled_error
in telemetry across versions 0.6.121 through 0.7.59 (~150-175/day).

Guard all pause sites through one helper (typeof check + swallow, plus
a once-per-page timeline_missing_pause analytics event so composition
authors can find the partial timeline), matching the safeVoid pattern
player.ts already uses. In the rebind restore path, pause is guarded
separately so a missing pause() no longer aborts the seek/play restore
behind it in the same try/catch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 01:58:39 -07:00
Vance Ingalls ff26e5f2c8 Merge pull request #2529 from heygen-com/via/resolution-portrait-fix
fix(cli): accept portrait aspects for --resolution alias flag
2026-07-16 01:54:55 -07:00
Vance Ingalls 8a793ba2b2 fix(studio): gate Layout and Style panel sections on the element having a rendered box
Both panels showed Layout (X/Y/W/H/Rotation/Z-index) unconditionally —
no gate existed for it at all — and Style was gated only on
canEditStyles (a permission check), never on the element's tag. Neither
gate accounted for `<audio>`, which never paints a visual frame, so a
music track's inspector showed a full set of position/size/fill/shadow
controls with zero visual effect.

Add `layout`/`style` applicability to resolveEditingSections (core),
keyed on tag !== "audio", and gate both panels' Layout section and the
existing Style gate on it. Media/Motion/Grade/Text were already
correctly gated (verified via a research pass across both panels) and
are untouched.
2026-07-16 01:47:46 -07:00
Vance Ingalls 14dbfca5a7 fix(studio): give flat Text section rows more vertical breathing room
6px between rows left little clearance above a row's value for the
promote-to-variable badge (now positioned above the row). Widen the
row gap to 10px.
2026-07-16 01:17:04 -07:00
Vance Ingalls 5f045b6bdf fix(studio): move the promote-to-variable badge above the row instead of a padding gutter
Shrinking the wrapped control's width to make room for the badge (previous
commit) fixed the overlap but pushed the value left unnecessarily. Move the
badge to sit above the row instead, clearing the value without touching its
layout.
2026-07-16 01:11:30 -07:00
Vance Ingalls 9fa1b26570 fix(studio): reserve a gutter for the promote-to-variable badge
PromotableControl absolutely-positions its "◇ var" / "◆ {id}" badge over
the wrapped control without reserving any space, so on rows where the
value renders flush to the right edge (flat Font/Color rows) the badge
sat directly on top of the value text instead of beside it. Add a
right-padding gutter on the wrapper sized to each badge state, and cap
the bound chip to a fixed max-width so it always fits inside its gutter.
2026-07-16 01:06:11 -07:00
ViaandClaude 2d398ed274 fix(cli): wire aspect-agnostic resolution through cloudrun/lambda/batch + preflight recompute
Addresses R2 CHANGES_REQUESTED from Miga + Rames on PR #2529:

1. Sibling-surface gap (blocker): `hyperframes cloudrun render{,-batch}`,
   `hyperframes lambda render{,-batch}` all advertised the same tier-only
   aliases (`1080p` / `hd` / `4k` / `uhd`) but normalized them to `landscape`
   and never set `outputResolutionAspectAgnostic`. The distributed plumbing
   PR #2529 added received `undefined` from those callers, so portrait `1080p`
   still hit the original aspect-mismatch on Cloud Run / Lambda.

   Fix: introduce `resolveResolutionFlagPair` in `@hyperframes/parsers` (the
   single source of truth for the two-step normalize + aspect-agnostic
   detect) and route every distributed entrypoint through a shared
   `parseOutputResolutionFlag` CLI util so the alias signal now reaches
   `SerializableDistributedRenderConfig`. Studio Server keeps its
   canonical-only HTTP contract; that intent is now pinned in tests.

2. Preflight recompute (hardening): the earlier "downgrade aspect-mismatch"
   preflight cleared un-remapped mismatches, so IG 4:5 (non-preset aspect,
   no sibling) and portrait-4K comp + `--resolution 1080p` (remap +
   downsample) both slipped through to fail late in `resolveDeviceScaleFactor`.
   Now `checkRenderResolutionPreflight` computes the effective preset via
   `suggestMatchingPreset` (mirroring the compile stage's
   `adaptAspectAgnosticResolution`) and re-checks against that — only
   genuinely-fixable mismatches clear early. New tests pin both regressed
   input classes.

3. Docker forwarding boundary test (Miga's important #2): pinned
   `1080p` survives verbatim as `--resolution 1080p` in the Docker args
   so the in-container CLI can re-run `isAspectAgnosticResolutionAlias`.

4. Doc-nit (Miga): parsers/src/types.ts no longer references the
   nonexistent `resolveResolutionForComposition` — points at the actual
   remap helpers.

Fallow: cloudrun.ts / lambda.ts share 390 lines of pre-existing structural
symmetry (parallel AWS + GCP dispatchers), and lambda/render.ts +
render-batch.ts declare parallel RenderArgs interfaces. Both re-flagged
after threading the aspect-agnostic field through each surface; ignored
with justification in .fallowrc.jsonc. lambda.ts's `run` and
lambda/render.ts's `waitForCompletion` are pre-existing CRAP-score
hotspots untouched by this PR — added under health.ignore.

Co-Authored-By: Claude <noreply@anthropic.com>

— Via
2026-07-16 08:01:56 +00:00
Vance Ingalls d5a259a3d2 fix(studio): make flat inspector's Stroke width free of style-name typing
Stroke width committed border-width and border-style together from one
free-text field, so setting a style meant typing an exact CSS keyword
(e.g. "dashed") with no indication of which ones were valid — the row
also duplicated the discoverable Stroke style select directly below it.
Stroke width now only commits border-width; style changes go exclusively
through the existing select.
2026-07-16 00:48:53 -07:00
Vance Ingalls b32f9a3e86 fix(studio): right-align FlatRow's value input in the flat inspector
FlatRow lays out label…gap…value across a `justify-between` row, but the
shared CommitField input it wraps had no text-align, so its text hugged
the LEFT edge of the value's own (often much wider) right-hand box —
looking left-aligned relative to the row, out of step with FlatSelectRow
and FlatSlider, which already right-align.

Added an optional `align` prop to CommitField (default "left", preserving
the legacy panel's MetricField/DetailField layouts where label-then-value
sits inline and left reads naturally) and pass `align="right"` from
FlatRow. Left the Motion Timing row's Start/End/Duration cells alone —
those stack label-above-value in a grid, a different pattern from the
inline label…value row this fix targets.

New tests: FlatRow's input has `text-right` (not `text-left`); the legacy
MetricField's input keeps `text-left` (not `text-right`), pinning
CommitField's default so the shared component doesn't drift for the
panel that didn't ask for this.

Full studio suite (2645 tests) green; typecheck/oxlint/oxfmt clean.
2026-07-16 00:48:53 -07:00
Vance Ingalls b0bb468877 Merge pull request #2519 from heygen-com/fix/studio-variable-promote-shared-session-sync
fix(studio): resync the shared SDK session after a Design-panel variable promote
2026-07-16 00:36:10 -07:00
Vance Ingalls d0d281de50 Merge pull request #2525 from heygen-com/via/audiopad-ffmpeg8-compat
fix(producer): audioPadTrim FFmpeg-8.x-compatible apad invocation
2026-07-16 00:35:53 -07:00
Vance Ingalls 3e4c761842 Merge pull request #2527 from heygen-com/via/artifact-validated-exit-gate
fix(cli): don't override exit code after artifact validated
2026-07-16 00:35:30 -07:00
Vance Ingalls 9d36bc9d95 Merge pull request #2526 from heygen-com/via/transcribe-timeout
feat(cli): configurable transcribe timeout with duration-scaled default
2026-07-16 00:35:08 -07:00
Vance Ingalls 578d6202b4 fix(studio): resync the shared SDK session after a Design-panel variable promote
Reported as "template variables are broken": binding an element's field to a
variable via the flat inspector's "◇ var" promote chip (or editing an
already-bound field's value) wrote the correct bytes to disk, but the
Variables tab kept showing the pre-edit value until the whole Studio page
was hard-reloaded.

Root cause: DesignPanelPromoteProvider deliberately opens its OWN SDK
session (`useSdkSession(projectId, selection.sourceFile ?? activeCompPath)`)
so that promoting inside a sub-composition binds the variable in the
sub-comp's own file, not the host's. For the common case — a top-level
element, same file as `activeCompPath` — this session is a SEPARATE
in-memory `Composition` instance from the shared one `VariablesPanel`
(Variables tab, Slideshow, etc.) reads. A persist through the promote
provider's session never fires the shared session's own "change" event.

Worse, the shared session's file-change listener runs
`isSelfWriteEcho(path, content)` to decide whether to reload — but
`sdkSelfWriteRegistry` is keyed by file path only, not by session instance
(its own doc comment assumes "the studio process has a single SDK session
lifecycle at a time"). It sees the promote provider's write registered
under the same path and concludes it's its own echo, permanently
suppressing the reload it actually needs.

Threaded `forceReloadSdkSession` (the same mechanism every other
server-side-write path in Studio already uses for exactly this "resync
after a write I didn't make myself" case) from App.tsx through
StudioRightPanel into DesignPanelPromoteProvider, and call it after every
successful promote/setDefault persist — unconditionally, not gated on the
promote target matching activeCompPath, since re-opening a file that
didn't change is a harmless no-op re-parse and a path-equality guard here
already produced one subtly wrong comparison (activeCompPath can be null
while the shared session still defaults to "index.html") before landing on
this simpler version. Verified live: editing a variable-bound field's
value now updates the Variables tab immediately, no reload required.

App.tsx crossed the 600-line file-size gate after threading the new prop;
extracted the tiny handleAddAssetAtPlayhead wrapper into its own
useAddAssetAtPlayhead hook (with a regression test) to bring it back under.

Full studio suite (2639 tests) green against a fresh main; typecheck/
oxlint/oxfmt clean.
2026-07-16 00:18:36 -07:00
ViaandClaude f3b941f1e3 fix(cli): reduce CRAP score by extracting exit-path helpers; bump contactSheet Windows timeout
Fallow flagged the `uncaughtException` arrow at CRAP 30.0 (right at
threshold). Extracting the three exit paths — emitCliErrorEvent,
reportPostRenderTerminationEvent, exitAfterPostRenderTermination,
exitAfterCliFailure — pulls the two `_flushSync?.()` optional-chain
branches out of the arrow body and drops cyclomatic to 3. Same shape
already used on compileStage in a sibling PR. Behavior preserved:
EPIPE → exit(0), renderSucceeded → report + flush + exit(0),
default → commandFailed + track + flush + exit(1) for uncaught;
renderSucceeded → report + return, default → commandFailed + track
for rejection.

Windows contactSheet flake: `createContactSheet > writes PNG output`
timed out at 20025ms (default ceiling 20000ms) on Windows. Test does
milliseconds of real work — the wall-clock overhead is sharp's native
binary fork under runner I/O jitter. PR #2492 already tried lightening
the test; the flake persists at the default ceiling. Bumping the
per-test timeout to 60s absorbs runner jitter without hiding real
slowdowns (a genuine sharp regression would blow through 60s just as
readily as 20s). Targeted per-test bump, not a global suite bump.

Co-Authored-By: Claude <noreply@anthropic.com>

— Via
2026-07-16 07:08:45 +00:00
ViaandClaude 40f4cfe92f fix(producer): materialize audioPadTrim concat script to real file
The prior fix (dc410ca) dropped `pathToFileURL` from the pad-concat step
to make FFmpeg 8.x on Windows stop rejecting `file:///C:/…` URLs — but
kept feeding the concat script via `pipe:0` stdin. That combination
broke Linux CI: FFmpeg's concat demuxer resolves bare paths in the
script against the base URL of the script's own source, and when the
script is fed via `pipe:0` the base URL is `pipe:`. Absolute POSIX
paths (`/tmp/foo.aac`) then join to `pipe:/tmp/foo.aac`, which the
demuxer tries to open as a pipe and fails with:

    [concat @ 0x…] Impossible to open 'pipe:/tmp/…/audio.aac'
    pipe:0: End of file

Manually reproduced with `ffmpeg-static@7.0.2` on this repo's binary.

Fix: write the concat script to a real temp file (`<outputPath>.concat-
list.txt`) and pass `-i concatListPath` — matching the sibling concat
in `distributed/assemble.ts:180-186` exactly. A real file's directory
becomes the base URL, so absolute paths in the script resolve as-is on
both Linux and Windows. The `file://` scheme prefix stays out of the
script (Windows FFmpeg 8.x fix preserved) and no `pipe:` prefix gets
prepended (Linux regression fixed). Cleanup path list now covers both
the silence tail and the concat list script.

Also drops the now-unused `runFfmpegWithStdin` helper — no consumer
needs stdin plumbing anymore.

Regression pins in `audioPadTrim.test.ts`:
  - `does not emit file:// URLs …` — Windows arg-shape pin (unchanged
    intent, moved from `stdin` to `concatListContent` field).
  - `materializes the pad-concat script to a real file …` — new pin
    that asserts `-i` is not `pipe:0` and points at the concat list
    path, so the Linux failure mode can't regress.

CI failures fixed:
  - CI / Producer: integration tests (assemble.test.ts pad case)
  - regression / regression-shards shard-1 (style-3-prod field-signal
    end-to-end render exercising the assemble pad path)

Co-Authored-By: Claude <noreply@anthropic.com>

— Via
2026-07-16 06:54:07 +00:00
ViaandClaude 7e58d050f8 fix(fallow): resolve audit findings for portrait --resolution PR
Three findings, all resolved:

- packages/producer/src/server.ts `render` (CRAP 31.6, cyclo 10 — minor):
  pre-existing complexity; the PR only threads
  `outputResolutionAspectAgnostic` through parseRenderOverrides /
  RenderInput and does not touch `render`. Line-shift fingerprint —
  exempted via health.ignore with justification comment.

- packages/producer/src/services/distributed/plan.ts `plan` (CRAP 36.7,
  cyclo 33 — major): pre-existing complexity; the PR only adds one
  optional field spread inside `plan` and does not add branches.
  Line-shift fingerprint — exempted via health.ignore with justification.

- packages/producer/src/services/render/stages/compileStage.ts
  `runCompileStage` (cyclo 23, cognitive 19 — minor): this one is a
  real complexity bump from the two-branch aspect-agnostic re-target
  block added in the fix. Extracted the block into a local helper
  `adaptAspectAgnosticResolution` so `runCompileStage` stays under both
  the cyclomatic (20) and cognitive (15) thresholds.

Verified locally with `fallow audit --base origin/main --fail-on-issues`
(exit 0, "No GitHub PR/MR findings") and `tsc --noEmit` on the producer
package.

Co-Authored-By: Claude <noreply@anthropic.com>

— Via
2026-07-16 06:52:11 +00:00
James Russo 21cb722ebd refactor(core): unify composition contract (#2157)
* refactor(core): unify composition contract

* fix(parsers): parse start expressions linearly
2026-07-16 02:44:22 -04:00
ViaandClaude 46e9ecf3f2 fix(cli): accept portrait aspects for --resolution alias flag
The aspect-agnostic resolution aliases (`--resolution 1080p` / `hd` / `4k` / `uhd`) previously all normalized to a landscape preset, which rejected portrait 1080x1920 compositions with 'Output resolution incompatible'. Users had to specify the orientation-bearing alias (`1080p-portrait`) or render at native.

This threads two new fields (`outputResolutionAspectAgnostic` + `outputResolutionRaw`) through the render pipeline. At the CLI layer we detect whether the user's flag was an aspect-agnostic alias; at the compile stage we re-map the preset to the composition's orientation via the existing `suggestMatchingPreset` sibling-lookup (formerly private). Explicit orientation-bearing aliases and canonical presets stay strict.

Field signal: ts=1784176662 (darwin/arm64, CLI 0.7.59, `--resolution 1080p` on a 1080x1920 portrait comp).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

— Via
2026-07-16 06:23:14 +00:00