Files
heygen-com__hyperframes/.fallowrc.jsonc
Miguel Ángel df29fa7a5e feat(studio): revamps Studio + improves code quality (#2291)
* feat(studio): glue API coexistence layer for the NLE swap

What: extends 21 glue files so the OLD timeline/canvas engine and the NEW
NLE components type-check side by side: playerStore (multi-select setters,
zoom pin, snap toggle, non-reactive scale scratch), drag-state types gain
optional NLE fields, timelineLayout/timelineAssetDrop/timelineEditingHelpers/
timelineEditing/timelineElementHelpers/studioHelpers/assetHelpers gain the
NLE exports, DomEditOverlay + gestures + AssetContextMenu + Timeline props
gain optional callbacks/params, contexts gain *Optional hooks, and
TimelineEditCallbacks.onMoveElements becomes a bivariant method accepting
both engines' change shapes. patchDocumentRootDuration's test rides along.

Why: this is the keystone that dissolves the old "welded glue" problem —
every symbol the NLE components need is ADDED next to what the old engine
still uses, so the engine components and the swaps can land as separate
reviewable PRs.

How: 15 authored intermediate files (main content + additive symbols; no
behavior changes — new fields optional, new callbacks unused until wired)
plus 6 files whose final content is already purely additive. New exports
without consumers yet carry TEMP(studio-dnd) ignoreExports entries, removed
by the app-shell swap.

Test plan: tsc --noEmit in studio + studio-server (verifies BOTH engines
compile); bunx vitest run (full suite green incl. the 6 new
patchDocumentRootDuration tests); fallow audit clean.

* feat(studio): timeline interaction hooks and lanes component (unwired)

What: the timeline-side wiring layer, unwired: TimelineLanes (the lane
renderer driving drag/resize/marquee), timelineMarquee (+tests),
useTimelineStackingSync, useTimelineGeometry, useTimelineEditPinning,
useTimelineEditingDrops.

Why: everything between the pure drag math and <Timeline> itself; the
timeline-glue swap PR then only rewires Timeline/TimelineCanvas onto these.

How: new files, tsc-clean against the coexistence layer. Unwired components
carry TEMP(studio-dnd) entry registrations, removed at the app-shell swap.

Test plan: bunx vitest run timelineMarquee.test.ts; tsc --noEmit; fallow
audit clean.

* feat(studio): NLE shell assembly (unwired)

What: EditorShell (the full editor layout replacing NLELayout +
StudioPreviewArea), TimelinePane (timeline host with sub-comp rebasing) and
useTimelineEditCallbacks (the callback bag bridging store edits to the
timeline), all unwired.

Why: the shell that App swaps to in the final step; reviewing it standalone
keeps that swap PR small.

How: new files against the coexistence layer; TEMP(studio-dnd) entries
until App mounts EditorShell in the app-shell swap.

Test plan: tsc --noEmit; bunx vitest run (suite unchanged); fallow audit
clean.

* feat(studio): timeline glue swap — Timeline/TimelineCanvas onto the NLE engine

What: flips the timeline glue to its final form (23 files): Timeline and
TimelineCanvas rebuilt on TimelineLanes/TimelineOverlays, useTimelineClipDrag
drives preview/commit through the new drag engine, range selection goes
multi-select, playback loop moves to useTimelinePlayerLoop. Deletes the 9
old-engine files this orphans (group drag, marquee selection, snap targets,
layer gutter, selection overlays + their suites) — each is compile- or
gate-forced by this swap, verified by probe.

Why: second swap step; timeline-only, canvas and App untouched.

How: modified files to final content + forced deletions.
playerStore/timelineEditing/timelineCallbacks stay at their coexistence
form until the app swap (the old App still runs on them).

Test plan: tsc --noEmit; bunx vitest run (full suite); fallow audit clean.

* feat(studio): clip thumbnail modules

What: ImageThumbnail (+tests) and thumbnailUtils (+tests) — frame decode
with SVG/AVIF format fallbacks and rounded-corner clipping — plus
VideoThumbnail updates.

Why: the decode layer for timeline clip thumbnails, ahead of the visual
refresh that renders them.

How: new modules + one modified file; purely presentational.

Test plan: bunx vitest run on both test files; tsc --noEmit; fallow audit
clean.

* feat(studio): assets/blocks panel behaviors + preview helpers

What: blocks tab install flow, right-panel and global drag-overlay polish,
music beat analysis and clip-content rendering hooks, and the
preview-helper utilities backing asset preview.

Why: completes the studio NLE stack on top of the visual refresh.

How: modified files only (kept as one PR: splitting further would produce
sub-150-LOC fragments of interdependent panel glue).

Test plan: bunx vitest run studioPreviewHelpers/studioUrlState suites; tsc
--noEmit; fallow audit clean.

* fix(studio): restore timeline playback loop

* fix(studio): restore missing GSAP helpers module

* refactor(studio): split timeline GSAP helpers

* style(studio): keep timeline helper under size limit

* fix(studio): restore timeline overlays module

* fix(studio): remove stale GSAP import

* fix(studio): restore canonical timeline dependencies

* style(studio): format restored timeline helpers

* style(studio): satisfy helper line limit

* fix(studio): repair rebuilt timeline integration

* feat(studio): complete rebuilt NLE cutover

* fix(studio): guard project and timeline race boundaries

* fix(studio): preserve graded resize and crop geometry

* fix(studio): log resize/rotate commit failures, move anchor accumulator to resize-local

* fix(studio): treat duration-0 tweens as static holds and settle resize position before persist

Instant holds (to()/fromTo() with duration 0) were classified as animated
tweens by every commit route, so resizing or rotating them converted the
hold into a corrupt duration-0 keyframes tween (new value at 0%, old at
100%) that GSAP drops; panel edits appended a losing set. A shared
isInstantHold() now routes them through the static replace-in-place path,
and percentage math guards zero-duration windows.

Separately, anchored-corner resizes painted 3-5 frames at the new size but
old position while the offset persist round-tripped the server. The commit
path now applies the corrected GSAP position synchronously before awaiting
the offset persist, mirroring the scale route's settle.

* feat(studio): gesture-transaction seam with commit observability

Introduce runGestureTransaction — one owner for a gesture commit's
settle -> persist -> record lifecycle. It settles the live DOM
synchronously before any async persist, folds every mutation into one
undo entry via a per-transaction coalesceKey, restores pre-gesture state
exactly once on failure, and asserts (dev console) + reports (PostHog:
commit_transaction / commit_invariant_violation / commit_transaction_failed)
that a persist never changes pixels. The box-size resize path is migrated
onto it; the ad hoc per-route coalesceKey/reload handling is removed.

Extract the resize draft-rect math into resizeDraft.ts to keep the
gesture-handler file under the size cap.

Also: keep url_hash telemetry to the route slug only (drop the query
string, which carried the user's selected element id/selector), and gate
the [hf-resize] diagnostics behind localStorage hf-resize-debug so they
ship as opt-in tracing rather than console noise.

* fix(studio): transaction owns the undo label

The coalesced history entry took the last sub-mutation's label, so a
resize surfaced as "Move layer" (the offset persist) in undo/redo. The
seam now stamps tx.label on every wrapped mutation, so the folded entry
reads as the gesture.

* fix(studio): atomic static size/position commits (no data loss)

Static resize/position holds updated an existing set via delete+add — two
undo entries, and a delete that succeeded before a failed add lost the
hold on disk. Use one in-place update-properties mutation when a set
exists (one undo entry, no partial-failure window). The keyframed-hold
heal that can't be expressed as a property update now adds before it
deletes, so any single failure leaves a recoverable duplicate, never a
lost hold. Transaction-owned commits are tracked via a WeakSet so the
heal path never double-wraps an already-wrapped gesture.

* fix(core): restore timed-clip visibility after a forced timeline rebind

__hfForceTimelineRebind force-rendered the re-registered timeline but never
re-ran the per-[data-start] visibility pass, so after undo or soft reload
every clip rendered regardless of its time window until a full page reload.
Extract the visibility loop into syncTimedElementVisibility and call it from
both syncMediaForCurrentState (unchanged) and the rebind.

* fix(studio): atomic z-order/keyframe/split commits, one undo entry each

Three edit-commit paths hardened onto the one-transaction invariant:

- Z-order reorder (useElementLifecycleOps): N per-element writes now fold
  into one undo entry (coalesceMs Infinity) and, on a failed persist,
  restore already-written files to disk so no partial reorder survives.
- Enable-keyframes (useEnableKeyframes/useGsapKeyframeOps): the intermediate
  convert phase no longer full-reloads the preview (skipReload), killing the
  black-flash remount; convert + edit share one coalesce key = one undo entry.
- Razor split-all (useRazorSplit): snapshot before the batch and restore on
  any failure, so a mid-batch error never leaves un-revertable partial splits.

Shared file-history helpers (RecordEditInput, DomEditCommitBaseParams,
readProjectFileContent, restoreFilesToOriginal) dedupe the rollback/commit
logic across these paths. Commit options thread as one partial object rather
than field-by-field. Test setup extracted into colocated helpers.

* fix(studio): fold multi-step edits into one undo entry; guard text revert

- Gesture recording (useGestureCommit): the per-property-group commits now
  share one coalesce key and only the last reloads, so a recording is one
  undo entry and one preview reload instead of up to four.
- Delete selected keyframes (deleteSelectedKeyframes, split out of
  timelineEditingHelpers): N removals fold into one coalesced undo entry
  with a single reload.
- Text-field commit (useDomEditTextCommits): commitDomTextFields now uses
  the same version-guarded revert as handleDomTextCommit, so a stale failed
  commit can no longer stomp a newer successful one.

* feat(studio): batch a gesture's mutations into one atomic server write

A transaction that emits N mutations previously did N sequential POSTs,
each rewriting the file and soft-reloading — the root of the multi-phase
persist window. Add a gsap-mutations-batch endpoint that validates every
mutation up front, applies them in one in-memory rewrite chain, and writes
the file once (all-or-nothing: an invalid entry rejects the whole batch,
no partial write). The seam buffers a transaction's commits and, when more
than one targets the same file, dispatches a single batch — one write, one
history entry, one reload. The batch capability rides on the existing
commit-function reference; no option fields are threaded through callers.

* fix(studio): soften off-canvas indicator outline to 30% opacity

The dashed off-canvas selection outline at 60% was noisy with many
protruding elements on screen; drop the resting opacity to 30% (hover
still restores full opacity so it stays discoverable).

* fix(studio): drop off-canvas indicator outline to 10% opacity

Follow-up to the 30% softening — 10% resting opacity reads much calmer
with many protruding elements; hover still restores full opacity.

* fix(studio): gate [hf-commit] console traces to dev only

The start/settled/persisted/restore lifecycle traces logged on every
gesture commit in all environments — console noise for end users. Route
them through a dev-only traceCommit helper (matching the pixel-violation
error's existing DEV gate). The commit_* PostHog events stay always on;
they are the production observability, the console lines are a dev aid.

* fix(studio): count actual reloads, not softReload requests, in commit telemetry

A resize's size and offset persists both request softReload; the seam
counted each request, so a batched gesture reported reload_count 2 even
though the batch is one write and one reload. Compute the count from what
dispatchBufferedCommits actually did — one for a batch, the request count
for the sequential fallback.

* fix(studio): rotate hover + off-canvas overlays with the element; flicker-free crop

- Hover overlay applied the element's rotation only to the selection chrome,
  not the hover box; it now rotates about center like the selection, via a
  shared orientedGroupAwareOverlayRect router (one owner for rotation-aware
  overlay geometry across hover/selection/off-canvas).
- Off-canvas indicator was axis-aligned; it now rotates with the element and
  inverse-rotates the canvas-exclusion clip into the element's local frame,
  so the protruding-sliver clip stays correct for rotated elements.
- Crop commit re-lifted the element only in the commit's .then(), so one
  frame painted the cropped state (the flicker). Re-lift synchronously right
  after onStyleCommit (which applies the clip before its first await), so the
  cropped state never paints; the persisted file value is unchanged.

* fix(studio): address code-review findings across the commit-hardening campaign

Correctness (would ship green, bite under latency):
- Enable-keyframes phase 2 now carries coalesceMs: Infinity, so the convert
  folds into one undo entry instead of splitting past the 300ms default.
- The SDK keyframe persist path forwards coalesceMs (CutoverOptions gains the
  field); multi-keyframe delete and convert coalesce correctly when SDK-routed.
- Razor split-all's rollback is guarded so a failing restore can't swallow the
  error toast that tells the user the split failed.

Simplification (single source of truth / no dead flexibility):
- Decompose resolveResizeDraftRect (drops a fallow-ignore suppression).
- Delegate the third readProjectFileContent copy to the shared helper.
- Inline setPatchFromUpdateProperties (its only caller passes one mutation).
- One toSdkPersistOptions translates gesture overrides to SDK options.
- Bundle the reorder-rollback deps into one object (was 7-9 positional args).
- Dedupe the 'last group reloads' ternary; type gesture options as
  CommitMutationOptions; drop a Map+array wrapper around a single write.

* feat(studio): atomic z-order reorder via batch patch-element endpoint

Z-order reorder issued N per-element inline-style patches (one server
write each), so a mid-chain failure could leave a partial reorder on disk.
Add a patch-elements-batch endpoint that validates every patch, folds them
over the file in one in-memory rewrite, and writes once (all-or-nothing;
unsafe input rejects with no write). The reorder now sends one batch per
source file and records one undo entry. Because a failed atomic write
persists nothing, the interim disk-write-back rollback (restoreReorderedFile
/ restoreFulfilledReorderFiles / ReorderRollbackDeps) is deleted — failure
rolls back only live DOM/store state. Closes the last disk-atomicity gap.

* fix(studio): razor-split undo no longer silently no-ops

The split clone was written to disk without a data-hf-id, so the split
endpoint recorded that unstamped HTML as the undo entry's afterHash. The
next reloadPreview() ran the preview route's ensureHfIds write-back, which
minted a fresh id and persisted DIFFERENT bytes — so at undo time the disk
hash no longer matched afterHash and editHistory's content-mismatch guard
silently refused the undo (no write, no network, no error). Stamp the split
output via ensureHfIds in splitElementInHtml before it is written/returned,
so the preview write-back is a no-op and the recorded afterHash always
equals the final on-disk bytes. Fixes at the source rather than relaxing the
mismatch guard. Corrects the stale comment that credited forceReloadSdkSession.

* feat(studio): closed-hand grab cursor on the rotate handle

The rotate handle used the default arrow cursor; show a grabbing
(closed-hand) cursor on hover to signal it's grabbed and dragged to rotate.

* fix(studio): dropping a dragged element over another no longer selects it

A moved drag's release fired the box click, which re-selected whatever now
sat under the pointer via the hover cache — so dropping an element over a
higher-z one selected the drop target instead of keeping the dragged
element selected. The drag-move branch now suppresses the next box click,
mirroring the resize branch.

* fix(studio): group drag is one undo entry, not one per element

Dragging a multi-selected group committed each member's position write as
its own undo entry, so reverting took N Cmd+Z presses. Force a shared
coalesceKey (infinite window) across every member's commit so they fold
into a single undo entry, like the other multi-step commit paths.

* fix(studio): undo of a split no longer leaves a ghost clip in the timeline

The file and the composition iframe revert correctly on undo, but the
timeline panel kept a ghost node for the split clone. The element-merge
that repopulates the timeline preserves elements the fresh scan dropped —
intended for enriched sub-composition children a bare DOM re-scan misses,
but it also preserved a genuinely-removed TOP-LEVEL element (the split
clone after undo), leaving a phantom clip. Restrict the preserve to
elements with a compositionSrc (the enriched sub-comp children); a
top-level element missing from the fresh scan was truly removed.

---------

Co-authored-by: ukimsanov <ular.kimsanov@heygen.com>
2026-07-13 02:55:36 -04:00

660 lines
35 KiB
JSON

{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/config-schema.json",
"entry": [
"packages/producer/src/**/*.test.ts",
"packages/aws-lambda/src/**/*.test.ts",
"packages/gcp-cloud-run/src/**/*.test.ts",
"packages/producer/src/regression-harness.ts",
"packages/producer/src/regression-harness-distributed.test.ts",
"packages/producer/src/regression-harness-lambda-local.ts",
"packages/producer/src/transparency-test.ts",
"packages/producer/src/parity-harness.ts",
"packages/producer/src/parity-fixtures.ts",
"packages/producer/src/perf-gate.ts",
"packages/producer/src/runtime-conformance.ts",
"packages/producer/src/benchmark.ts",
"packages/producer/scripts/generate-font-data.ts",
"packages/producer/we-render.mjs",
"packages/producer/scripts/validate-fast-video.ts",
"packages/cli/scripts/generate-font-data.ts",
"packages/engine/scripts/test-fitTextFontSize-browser.ts",
"packages/aws-lambda/scripts/*.ts",
// Built as standalone IIFE for the browser-side sandbox runtime;
// referenced by file path (not import) in build-hyperframes-runtime-artifact.ts.
"packages/core/src/runtime/entry.ts",
// Bundled as a standalone IIFE by the position-edits render artifact
// generator; referenced by file path rather than imported by TypeScript.
"packages/core/stubs/position-edits-render-entry.ts",
// In-page audit scripts read as raw strings and injected via
// page.addScriptTag (see layout.ts / validate.ts) — referenced by file
// path, never imported, so they have no import-graph referrer.
"packages/cli/src/commands/layout-audit.browser.js",
"packages/cli/src/commands/contrast-audit.browser.js",
"packages/cli/src/commands/motion-sample.browser.js",
// Worker entry points loaded dynamically by their *Pool.ts companions.
"packages/producer/src/services/pngDecodeBlitWorker.ts",
"packages/producer/src/services/shaderTransitionWorker.ts",
// Off-main-thread /health endpoint, spawned by path from healthWorker.ts.
"packages/producer/src/services/healthWorkerThread.ts",
// Test fixture worker, spawned by path via the pools' workerEntryPath
// option from the crash-recovery tests; has no import-graph referrer.
"packages/producer/src/services/__fixtures__/crashOnMessageWorker.mjs",
"scripts/*.{ts,mjs,js}",
"scripts/*/run.mjs",
// Keyframe UI components — wired dynamically via EaseCurveSection/MotionPanel.
"packages/studio/src/components/editor/KeyframeDiamond.tsx",
"packages/studio/src/components/editor/SpringEaseEditor.tsx",
// NLE notice — rendered conditionally via NLEContext/EditorShell when timeline is first shown.
"packages/studio/src/components/nle/TimelineEditorNotice.tsx",
// Zoom hook extracted for downstream razor-blade PRs (#1330, #1331).
"packages/studio/src/player/components/useTimelineZoom.ts",
// Cached O(1) GSAP target lookup, replacing O(n²) inline checks.
// Consumers migrate in a follow-up once useDomGeometryCommits adopts it.
"packages/studio/src/hooks/gsapTargetCache.ts",
// Preview helper consumed dynamically from the studio iframe bridge.
"packages/studio/src/hooks/gsapRuntimePreview.ts",
],
"ignorePatterns": [
"docs/**",
// Chrome browser binaries downloaded by puppeteer — not project source.
"chrome/**",
// Standalone spike scripts and benchmark runners used during R&D.
"packages/engine/spikes/**",
"packages/producer/de-*.mjs",
"packages/producer/tests/**",
"packages/player/tests/**",
"packages/engine/tests/**",
"skills/**/test-corpus/**",
"skills/**/scripts/**",
// Agent-invoked motion-graphics tools co-located with their docs (run via
// `node <path>` per grounding/PROTOCOL.md / categories/maps/module.md
// prose), not import-graph reachable.
"skills/motion-graphics/grounding/**",
"skills/motion-graphics/categories/**",
// Agent-invoked reference materials (template + motion-primitive HTML, catalogs),
// forked by path by the frame-worker per SKILL.md prose, not import-graph reachable.
"skills/music-to-video/references/**",
// Bundled @font-face data (read at runtime via fs.readFileSync, invisible
// to the import graph) + its manual rebuild tool.
"skills/**/fonts/**",
// Golden snapshot files: data consumed by toMatchFileSnapshot, not importable modules.
"packages/**/__goldens__/**",
"registry/**",
"examples/**",
"packages/sdk/examples/**",
".github/workflows/fixtures/**",
// Auto-generated TS client for the HeyGen cloud API. Regenerated by
// experiment-framework/scripts/generate_hyperframes_cli_client.py via
// the sync-hyperframes-codegen.yml workflow; complexity/dead-code
// findings on this file are not actionable from this repo.
"packages/cli/src/cloud/_gen/**",
],
"ignoreExports": [
// Part of useTimelineEditing's inferred public return type; consumers invoke
// handleTimelineGroupResize without importing the change type directly.
{
"file": "packages/studio/src/hooks/useTimelineGroupEditing.ts",
"exports": ["TimelineGroupResizeChange"],
},
{
"file": "packages/studio/src/player/components/timelineGroupEditing.ts",
"exports": [
"buildTimelineGroupResizeMembers",
"resolveTimelineGroupResizeChanges",
"applyTimelineGroupResizePreview",
],
},
{
"file": "packages/studio/src/player/components/timelineEditing.ts",
"exports": [
"selectTimelineElementsInMarquee",
"TimelineGroupResizeEdge",
"TimelineGroupTimingMember",
],
},
{
"file": "packages/studio/src/player/lib/timelineElementHelpers.ts",
"exports": ["furthestClipEndFromDocument", "furthestClipEndFromSource"],
},
{
"file": "packages/studio/src/components/sidebar/AssetContextMenu.tsx",
"exports": ["DeleteConfirm"],
},
{
"file": "packages/studio/src/utils/timelineAssetDrop.ts",
"exports": [
"setCompositionDurationToContent",
"extendCompositionDurationIfNeeded",
"fitTimelineAssetGeometry",
"resolveTimelineAssetCompositionSize",
],
},
{
"file": "packages/studio/src/components/sidebar/assetHelpers.ts",
"exports": ["truncateMiddle", "formatDuration"],
},
{
"file": "packages/studio/src/utils/studioHelpers.ts",
"exports": ["resolveDroppedAssetDimensions"],
},
// drawElementService is the bottom of the fast-capture Graphite stack
// (#1917): its consumers (frameCapture in #1919) land two PRs upstack, so
// a per-PR audit diffing against the merge base sees these exports as
// unused. Consumed for real once the stack merges; safe to drop this
// entry after #1919 lands.
{
"file": "packages/engine/src/services/drawElementService.ts",
"exports": [
"instrumentAcceleratedCanvases",
"injectDrawElementCanvas",
"captureDrawElementFrame",
"initDrawElementWorkerEncode",
"cleanupDrawElementWorkerEncode",
"produceDrawElementFrame",
],
},
// CLI command files: every command exports a const `examples` per the
// convention documented in CLAUDE.md. This is a namespace barrel, not a
// collision.
{ "file": "packages/cli/src/commands/*.ts", "exports": ["examples"] },
// Options type for the render-queue hook's public startRender callback —
// consumed structurally by callers (StudioRightPanel), not by import.
{
"file": "packages/studio/src/components/renders/useRenderQueue.ts",
"exports": ["StartRenderOptions"],
},
// Independent ML model managers each declare their own DEFAULT_MODEL /
// MODELS_DIR / ensureModel for their model namespace.
{
"file": "packages/cli/src/{background-removal,tts,whisper}/manager.ts",
"exports": ["DEFAULT_MODEL", "MODELS_DIR", "ensureModel"],
},
// `isPathInside` is documented as exported-for-tests only in fileServer.ts;
// it has different semantics (symlink resolution) from utils/paths.ts.
{
"file": "packages/producer/src/services/fileServer.ts",
"exports": ["isPathInside"],
},
// Studio telemetry: consumed by useRenderQueue.ts / StudioFeedbackBar.tsx
// (deep relative imports) but fallow's static analyzer doesn't trace
// them. Same path-resolution quirk — trackStudioSessionStart from the
// same file resolves fine.
{
"file": "packages/studio/src/telemetry/events.ts",
"exports": ["trackStudioRenderStart", "trackStudioFeedback"],
},
// domEditingLayers: these exports are consumed via the browser iframe
// runtime context (not traceable by static import analysis from the
// studio entry point) or re-exported through the domEditing barrel but
// have no downstream consumers yet.
{
"file": "packages/studio/src/components/editor/domEditingLayers.ts",
"exports": [
"isEditableTextLeaf",
"collectDomEditTextFields",
"buildElementLabel",
"refreshDomEditSelection",
],
},
// domEditing barrel: re-exports consumed throughout the studio but
// fallow's static analyzer can't trace re-exports through barrel files.
{
"file": "packages/studio/src/components/editor/domEditing.ts",
"exports": ["*"],
},
// Exported for render.test.ts (exported-for-tests pattern).
{
"file": "packages/cli/src/commands/render.ts",
"exports": ["resolveBrowserGpuForCli", "renderLocal", "checkRenderResolutionPreflight"],
},
// initThreeDProjectionInPage: passed to page.evaluate() for browser-side execution —
// Puppeteer serializes it at runtime, not an import-graph consumer.
{
"file": "packages/engine/src/services/threeDProjection.ts",
"exports": ["initThreeDProjectionInPage"],
},
// captureCost.ts: constants and helpers consumed by the runCaptureCalibration
// orchestration function and tests, but the entry-point graph doesn't
// reach them because the orchestrator's caller resolves them dynamically.
{
"file": "packages/producer/src/services/render/captureCost.ts",
"exports": [
"CAPTURE_CALIBRATION_TARGET_MS",
"MAX_MEASURED_CAPTURE_COST_MULTIPLIER",
"CAPTURE_CALIBRATION_PROTOCOL_TIMEOUT_MS",
"measureCaptureCostFromSession",
"logCaptureCalibrationResult",
"createFailedCaptureCalibrationEstimate",
],
},
// gsapParserExports.ts is the public-API barrel that re-exports constants,
// types, and utilities from gsapConstants, gsapSerialize, and springEase.
// The re-exports are intentional public API consumed by callers outside the
// changed-file set (e.g. studio, aws-lambda) and therefore appear unused
// to fallow's static analysis of the PR diff.
{
"file": "packages/parsers/src/gsapParserExports.ts",
"exports": [
"PROPERTY_GROUPS",
"classifyPropertyGroup",
"classifyTweenPropertyGroup",
"SPRING_PRESETS",
"generateSpringEaseData",
"GsapMethod",
"GsapKeyframesData",
"GsapKeyframeFormat",
"PropertyGroupName",
"SpringPreset",
],
},
// Shared test helpers consumed by gsapParser.test.ts (same file,
// fallow doesn't trace intra-file test consumption).
{
"file": "packages/parsers/src/gsapParser.test-helpers.ts",
"exports": [
"expectKeyframe",
"expectKeyframesFormat",
"convertAndReparse",
"parseSplitAndAssert",
],
},
// hfIds: EXCLUDED_TAGS is consumed by tests (htmlParser.test.ts) and
// hfIdPersist.ts but fallow's static analyzer may not trace all consumers.
{
"file": "packages/parsers/src/hfIds.ts",
"exports": ["EXCLUDED_TAGS", "mintHfId"],
},
// Shared timeline components extracted for downstream PRs in the
// razor-blade stack (#1330, #1331). Consumers live on those branches.
{
"file": "packages/studio/src/player/components/timelineCallbacks.ts",
"exports": ["*"],
},
// Shared timeline DOM barrel for downstream consumers; fallow does not
// trace all re-export-only modules.
{
"file": "packages/studio/src/player/lib/timelineDOM.ts",
"exports": ["*"],
},
// gsapTargetCache: cached O(1) GSAP target lookup, consumed by
// useDomEditCommits and intended to replace the local copy in
// useDomGeometryCommits once callers migrate.
{
"file": "packages/studio/src/hooks/gsapTargetCache.ts",
"exports": ["isElementGsapTargeted"],
},
// Re-exports from useDomEditCommits: barrel-style re-exports
// consumed by downstream studio code.
{
"file": "packages/studio/src/hooks/useDomEditCommits.ts",
"exports": ["GSAP_CSS_FALLBACK_BLOCKED_MESSAGE", "PersistDomEditOperations"],
},
{
"file": "packages/studio/src/utils/timelineElementSplit.ts",
"exports": ["buildPatchTarget", "readFileContent"],
},
// freezeUrl and freezeLocalFile are public API re-exported from the figma
// barrel (index.ts) for Task 4 manifest flow and the /figma skill integration;
// not yet imported by current code outside the module.
{
"file": "packages/core/src/figma/freeze.ts",
"exports": ["freezeUrl", "freezeLocalFile"],
},
// mediaDir, typeDirPath, isFigmaManifestRecord: re-exported from the figma
// barrel (index.ts) via manifest.ts per Task 8 wiring. Consumed only by the
// /figma skill integration, not by code in the current codebase.
{
"file": "packages/core/src/figma/manifest.ts",
"exports": ["mediaDir", "typeDirPath", "isFigmaManifestRecord"],
},
],
"ignoreDependencies": [
// Runtime/dynamic deps not visible to static analysis: tsup `external`,
// dynamic require() resolution, peer/static-file consumption in tests,
// and bun-hoisted workspace devDeps (e.g. happy-dom in root package.json
// resolves for every workspace, so workspaces don't redeclare it).
// Required by @puppeteer/browsers and puppeteer-core at runtime; listed
// as a direct dep to guarantee installation even when transitive
// resolution fails (corrupted cache, dedup edge cases).
"debug",
"puppeteer",
"puppeteer-core",
"esbuild",
"giget",
"gsap",
"happy-dom",
"ffmpeg-static",
"ffprobe-static",
"@hyperframes/core",
"@hyperframes/studio",
"@hyperframes/producer",
"@fontsource/archivo-black",
"@fontsource/eb-garamond",
"@fontsource/ibm-plex-mono",
"@fontsource/inter",
"@fontsource/jetbrains-mono",
"@fontsource/league-gothic",
"@fontsource/montserrat",
"@fontsource/nunito",
"@fontsource/oswald",
"@fontsource/outfit",
"@fontsource/space-mono",
"@fontsource/lato",
"@fontsource/noto-sans-jp",
"@fontsource/open-sans",
"@fontsource/playfair-display",
"@fontsource/poppins",
"@fontsource/roboto",
"@fontsource/source-code-pro",
],
"duplicates": {
// Raise from the default 5 to 6 lines so trivially short Hono route-handler
// preambles (resolveProject + 404 + body-parse) are below the threshold.
// The three 5-line groups in files.ts / render.ts are structural boilerplate
// that naturally converges and is unlikely to diverge; extraction would
// require intrusive middleware changes beyond this PR's scope.
"minLines": 6,
"ignore": [
// sourcePatcher.ts: pre-existing internal clones between the inline-style
// and attribute tag-patchers; only the PatchOperation type gained two
// optional fields here, but the line shift makes fallow re-flag them.
"packages/studio/src/utils/sourcePatcher.ts",
// useGsapSelectionHandlers.ts: pre-existing parallel structure with
// useDomEditWiring.ts (thin useCallback wrappers guarding on selection);
// only gained two optional pass-through parameters here, but the line
// shift makes fallow re-flag the pre-existing clone.
"packages/studio/src/hooks/useGsapSelectionHandlers.ts",
// gsapParser.ts: recast/babel GSAP writer — intentional duplication between
// recast and acorn parallel implementations (pre-existing, moved from core).
"packages/parsers/src/gsapParser.ts",
// hfIds.ts: 7-line clone with sdk/engine/mutate.ts — pre-existing duplication
// from when hfIds lived in packages/core/src/parsers/. Moving the file to the
// new package makes fallow see it as a fresh finding; the underlying clone
// predates this refactor.
"packages/parsers/src/hfIds.ts",
// Parser test files: parallel arrange/act/assert test cases — pre-existing
// duplication moved from packages/core/src/parsers/.
"packages/parsers/src/gsapParser.test.ts",
"packages/parsers/src/gsapParser.test-helpers.ts",
"packages/parsers/src/gsapWriter.parity.test.ts",
"packages/parsers/src/gsapWriterParity.corpus.test.ts",
"packages/parsers/src/gsapWriterParity.acorn.test.ts",
"packages/parsers/src/htmlParser.roundtrip.test.ts",
"packages/parsers/src/htmlParser.test.ts",
// @hyperframes/studio-server test files: parallel arrange/act/assert test cases
// (pre-existing structure from when studio-api lived in packages/core/src/studio-api/).
"packages/studio-server/src/routes/files.test.ts",
"packages/studio-server/src/routes/render.test.ts",
"packages/studio-server/src/routes/lint.test.ts",
"packages/studio-server/src/routes/preview.test.ts",
"packages/studio-server/src/routes/projects.test.ts",
"packages/studio-server/src/helpers/backupJournal.test.ts",
"packages/studio-server/src/helpers/finiteMutation.test.ts",
"packages/studio-server/src/helpers/hfIdPersist.test.ts",
"packages/studio-server/src/helpers/manualEditsRenderScript.test.ts",
"packages/studio-server/src/helpers/mediaValidation.test.ts",
"packages/studio-server/src/helpers/previewAdapter.test.ts",
"packages/studio-server/src/helpers/safePath.test.ts",
"packages/studio-server/src/helpers/sourceMutation.test.ts",
"packages/studio-server/src/helpers/studioMotionRenderScript.test.ts",
"packages/studio-server/src/helpers/subComposition.test.ts",
// @hyperframes/lint rule test files: parallel arrange/act/assert test cases
// (pre-existing structure from when lint lived in packages/core/src/lint/).
"packages/lint/src/rules/adapters.test.ts",
"packages/lint/src/rules/captions.test.ts",
"packages/lint/src/rules/composition.test.ts",
"packages/lint/src/rules/core.test.ts",
"packages/lint/src/rules/fonts.test.ts",
"packages/lint/src/rules/gsap.test.ts",
"packages/lint/src/rules/media.test.ts",
"packages/lint/src/rules/slideshow.test.ts",
"packages/lint/src/rules/textures.test.ts",
"packages/lint/src/hyperframeLinter.test.ts",
// slideshowPanelHelpers.ts: setSlideNotes/addFragment/addHotspot share an
// intentional parallel shape (signature + mapSlidesIn → exists-check →
// map/append); the per-slide mutation differs, so a shared abstraction
// would obscure more than it dedupes.
"packages/studio/src/components/panels/slideshowPanelHelpers.ts",
// SlideshowPanel.test.ts: parallel arrange/act/assert test cases — collapsing
// them would hurt readability of what each case verifies.
"packages/studio/src/components/panels/SlideshowPanel.test.ts",
// hyperframes-player.test.ts: parallel arrange/act/assert test cases verifying
// distinct behaviors (same-origin vs realm media, audio-locked permutations,
// seek bridge variants). Each case is self-contained for readability;
// extracting the iframe / mock-audio setup helpers would over-couple
// unrelated scenarios under a shared fixture.
"packages/player/src/hyperframes-player.test.ts",
// present.ts mirrors play.ts's server startup + console-output block. The
// shared low-level pieces (resolve*/injectRuntime/listenOnFreePort) are in
// utils/compositionServer.ts; the remaining clone is per-command logging text
// (different labels/help lines) — extracting it would over-abstract.
"packages/cli/src/commands/present.ts",
// skillsManifest.test.ts: parallel arrange/act/assert cases for locateInstall
// (project vs global scope, per-agent host conventions, claude-code priority).
// Each case seeds a dir then asserts the resolved location/agent; collapsing
// the shared seed/assert shape would obscure what each scope/host verifies.
"packages/cli/src/utils/skillsManifest.test.ts",
// skills.test.ts: parallel prune cases (removed-in-global vs project, non-slug
// rejection, --source/--dir plumbing) share a mock-checkSkills → runSkillsUpdate
// → assert-remove-spawn shape; each verifies a distinct prune behavior, so
// extracting the shared scaffold would obscure what each case asserts.
"packages/cli/src/commands/skills.test.ts",
// figma test files: freeze.test.ts and manifest.test.ts share a common
// arrange/act/assert setup preamble (file creation + test dir handling) that
// is minimal boilerplate; collapsing it into a shared fixture would reduce
// readability of each test's independent setup.
"packages/core/src/figma/freeze.test.ts",
"packages/core/src/figma/manifest.test.ts",
// layout-audit.browser.test.ts: parallel arrange/act/assert cases per audit
// rule (overflow / overlap / occlusion) that each install their own mocked
// geometry + computed-style before asserting. The clone groups are this
// pre-existing per-rule scaffold; adding a clip-path case shifts lines and
// re-flags it. Collapsing the per-rule installers would obscure each case.
"packages/cli/src/commands/layout-audit.browser.test.ts",
// gsapParserAcorn.motionEval.test.ts: parallel arrange/act/assert cases for
// the staggered-collection honesty pass (.from reveal vs .to landing on the
// rest pose). Each asserts a distinct keyframe shape; collapsing the shared
// parse/keyframes-extraction scaffold would obscure what each case verifies.
"packages/core/src/parsers/gsapParserAcorn.motionEval.test.ts",
// Studio UX-review sweep (148 findings fixed across ~90 studio files):
// heavily-edited files re-flag line-shifted inherited complexity, and the
// new small handlers (a11y keydown/menu/dialog/error-state paths, mostly
// 5-6 cyclomatic) trip the CRAP threshold absent coverage data. Reviewed
// individually in the studio-ux PR stack rather than refactored here.
"packages/studio/src/player/components/TimelineClipDiamonds.test.tsx",
"packages/studio/src/hooks/useFileManager.ts",
"packages/studio/src/captions/hooks/useCaptionSync.ts",
"packages/studio/vite.adapter.ts",
"packages/studio/src/components/sidebar/AudioRow.tsx",
"packages/studio/src/components/sidebar/AssetsTab.tsx",
"packages/studio/src/components/editor/propertyPanelFill.tsx",
"packages/studio/src/captions/store.ts",
"packages/studio/src/captions/components/CaptionOverlay.tsx",
"packages/cli/src/server/studioServer.ts",
// Sub-composition html/body scoping fix: inlineSubCompositions.ts has a
// pre-existing head-vs-content script-extraction clone, and the scoping
// test file has pre-existing parallel arrange/act/assert cases. Adding the
// scopeRootSelectors handling shifts lines and re-flags both.
"packages/core/src/compiler/inlineSubCompositions.ts",
"packages/core/src/compiler/compositionScoping.test.ts",
],
},
"health": {
// useGsapTweenCache.ts: pre-existing large React-effect hooks (the populate
// and runtime-scan effects, the per-element animations memo) whose
// complexity pre-dates the computed-timeline work. Exempted at file level
// rather than refactored as scope creep.
"ignore": [
// sourcePatcher.ts: resolveSourceFile / splitInlineStyleDeclarations /
// patch*InTag pre-date this PR; only the PatchOperation type gained two
// optional fields, but the line-shift fingerprint re-flags the inherited
// complexity.
"packages/studio/src/utils/sourcePatcher.ts",
// timeline.ts: collectRuntimeTimelinePayload (CRITICAL) pre-dates this PR;
// only an import line changed here (slideshow/sceneId → slideshow/index),
// but the line-shift fingerprint makes fallow re-flag inherited complexity.
"packages/core/src/runtime/timeline.ts",
// sceneId.ts: trivial 5-cyclomatic guard, moved verbatim from
// packages/core/src/slideshow/ into parsers; flagged only because the move
// makes fallow treat it as a fresh finding.
"packages/parsers/src/slideshow/sceneId.ts",
// assetPaths.ts / rewriteSubCompPaths.ts: pure URL/asset-path helpers moved
// verbatim from packages/core/src/compiler/ into parsers. The move makes
// fallow score them as fresh (high CRAP = inherited complexity with no
// coverage mapping yet); the logic is unchanged.
"packages/parsers/src/assetPaths.ts",
"packages/parsers/src/rewriteSubCompPaths.ts",
// gsapParser.ts: the recast/babel GSAP writer is a 2500-line legacy parser;
// moved from packages/core/src/parsers/ — same complexity rationale.
"packages/parsers/src/gsapParser.ts",
// htmlParser.ts has pre-existing complexity (moved from packages/core).
"packages/parsers/src/htmlParser.ts",
// studio-server files: pre-existing complexity (moved from packages/core/src/studio-api/).
// files.ts: executeGsapMutationRecast/Acorn are CRITICAL; excluded as files.ts
// was already in health.ignore at the old path (packages/core/src/studio-api/routes/files.ts).
"packages/studio-server/src/routes/files.ts",
"packages/studio-server/src/routes/render.ts",
"packages/studio-server/src/routes/thumbnail.ts",
"packages/studio-server/src/helpers/manualEditsRenderScript.ts",
"packages/studio-server/src/helpers/studioMotionRenderScript.ts",
"packages/studio-server/src/helpers/subComposition.ts",
// lint rule implementations and project linter: pre-existing complexity
// (moved from packages/core/src/lint/). File-level exemption avoids the
// line-shift fingerprint problem for inherited findings.
"packages/lint/src/rules/media.ts",
"packages/lint/src/rules/textures.ts",
"packages/lint/src/rules/gsap.ts",
"packages/lint/src/project.ts",
// SlideshowPanel.tsx: top-level editor panel that wires several independent
// sections (slides/inspector/branches/hotspot). Its cyclomatic count comes
// from that fan-out; splitting it would scatter shared state without
// reducing real complexity. File-level exemption (not an inline comment)
// avoids the line-shift fingerprint problem noted above.
"packages/studio/src/components/panels/SlideshowPanel.tsx",
// play.ts / present.ts: CLI command entrypoints whose cyclomatic count is
// browser/arg validation + server wiring (same shape as preview.ts). The
// serving logic is factored into utils/compositionServer.ts; the remaining
// body is linear validation that reads clearly inline.
"packages/cli/src/commands/play.ts",
"packages/cli/src/commands/present.ts",
// sync-agent-dirs.ts: a build-time codegen that regex-parses upstream
// agents.ts. parseAgents/resolveGlobalExpr are branchy by nature (literal
// vs base-var args, validation throws) but small and well-tested via the
// generated table's shape test; this is dev tooling, not shipped runtime.
"packages/cli/scripts/sync-agent-dirs.ts",
// Files modified only for import-path updates (one-line changes to switch
// from @hyperframes/core/* subpaths to the new packages). Their complexity
// is pre-existing; the line-shift fingerprint problem makes fallow treat
// the violations as new even though no logic changed.
"packages/cli/src/server/studioServer.ts",
"packages/core/src/core.types.ts",
"packages/core/src/generators/hyperframes.ts",
"packages/producer/src/services/htmlCompiler.ts",
"packages/studio/src/hooks/gsapRuntimeBridge.ts",
"packages/studio/src/hooks/gsapShared.ts",
"packages/studio/src/hooks/gsapDragPositionCommit.ts",
"packages/studio/src/hooks/gsapKeyframeCacheHelpers.ts",
"packages/studio/vite.config.ts",
"packages/cli/src/commands/lint.ts",
"packages/cli/src/commands/preview.ts",
"packages/cli/src/commands/publish.ts",
"packages/cli/src/server/studioServer.ts",
// set-version.ts: compareSemver helper has pre-existing complexity from
// semver string parsing logic; line-shift fingerprint problem from new
// packages added to PACKAGES array makes fallow treat it as new.
"scripts/set-version.ts",
// gsapRuntimeReaders.ts: pre-existing complexity in readAllAnimatedProperties;
// line-shift fingerprint from import-path updates triggers the violation.
"packages/studio/src/hooks/gsapRuntimeReaders.ts",
// In-page audit scripts: single large browser-side IIFE wrappers
// (auditLayout/__contrastAudit) with pre-existing CRITICAL helpers
// (textOverflowFixHint, selectorFor, the WCAG walk). Adding the small
// clip-path probe helpers shifts every function below it, so the line-shift
// fingerprint re-flags inherited complexity even though the new helpers are
// all at the lowest tier (<=5 cyclomatic).
"packages/cli/src/commands/layout-audit.browser.js",
"packages/cli/src/commands/contrast-audit.browser.js",
// lottie.ts: the flagged `seek` handler (lottie-web/dotLottie dual-API
// dispatch, unchanged by the duration-auto-inference work in this PR)
// pre-dates this PR's scope. New functions added earlier in the file
// (getInferredDurationSeconds + helpers) shifted its line numbers,
// breaking fallow's inherited-detection fingerprint. File-level
// exemption avoids the line-shift problem for this inherited finding.
"packages/core/src/runtime/adapters/lottie.ts",
// info.ts: the flagged `run` command handler pre-dates the keyframes-cli
// PR; that PR only added the orientation()/durationFromHtml() helpers and
// swapped a few `maxEnd` reads for the new `duration` variable, without
// adding branches. The line-shift fingerprint re-flags the inherited
// complexity even though the logic is unchanged.
"packages/cli/src/commands/info.ts",
// gsapParserAcorn.ts: the flagged `sameMemberAccess` (structural equality
// of two member-access nodes) pre-dates this PR's motion-eval work
// (added in #1760, unchanged since). The new const-folding / set-seeding /
// label-position code added earlier in the file shifted its line number,
// so the line-shift fingerprint re-flags this inherited finding.
"packages/parsers/src/gsapParserAcorn.ts",
// isFigmaManifestRecord: type guard with 19 cyclomatic due to chained
// field validation (id/type/path/source types + value shape guards).
// This is the correct shape for a discriminating type guard; refactoring
// into smaller guards would obscure the unified validation contract.
"packages/core/src/figma/manifest.ts",
// Studio UX-review sweep (148 findings fixed across ~90 studio files):
// heavily-edited files re-flag line-shifted inherited complexity, and the
// new small handlers (a11y keydown/menu/dialog/error-state paths, mostly
// 5-6 cyclomatic) trip the CRAP threshold absent coverage data. Reviewed
// individually in the studio-ux PR stack rather than refactored here.
"packages/studio/src/App.tsx",
"packages/studio/src/captions/components/CaptionAnimationPanel.tsx",
"packages/studio/src/captions/components/CaptionOverlay.tsx",
"packages/studio/src/captions/components/CaptionOverlayUtils.ts",
"packages/studio/src/captions/components/CaptionPropertyPanel.tsx",
"packages/studio/src/captions/components/shared.tsx",
"packages/studio/src/captions/hooks/useCaptionSync.ts",
"packages/studio/src/components/AskAgentModal.tsx",
"packages/studio/src/components/editor/BlockParamsPanel.tsx",
"packages/studio/src/components/editor/DomEditOverlay.tsx",
"packages/studio/src/components/editor/FileTree.tsx",
"packages/studio/src/components/editor/FileTreeNodes.tsx",
"packages/studio/src/components/editor/LayersPanel.tsx",
"packages/studio/src/components/editor/MotionPathNode.tsx",
"packages/studio/src/components/editor/PropertyPanel.tsx",
"packages/studio/src/components/editor/propertyPanelFont.tsx",
"packages/studio/src/components/editor/propertyPanelMediaSection.tsx",
"packages/studio/src/components/editor/propertyPanelStyleSections.tsx",
"packages/studio/src/components/editor/Transform3DCube.tsx",
"packages/studio/src/components/LintModal.tsx",
"packages/studio/src/components/MediaPreview.tsx",
"packages/studio/src/components/nle/NLEPreview.tsx",
"packages/studio/src/components/sidebar/AudioRow.tsx",
"packages/studio/src/components/sidebar/BlocksTab.tsx",
"packages/studio/src/components/sidebar/CompositionsTab.tsx",
"packages/studio/src/components/sidebar/LeftSidebar.tsx",
"packages/studio/src/components/storyboard/StoryboardLoaded.tsx",
"packages/studio/src/components/StudioRightPanel.tsx",
"packages/studio/src/components/StudioToast.tsx",
"packages/studio/src/components/ui/Tooltip.tsx",
"packages/studio/src/components/ui/useDialogBehavior.ts",
"packages/studio/src/hooks/useAppHotkeys.ts",
"packages/studio/src/hooks/useCaptionDetection.ts",
"packages/studio/src/hooks/useFileManager.ts",
"packages/studio/src/hooks/useFrameCapture.ts",
"packages/studio/src/hooks/usePanelLayout.ts",
"packages/studio/src/player/components/menuKeyboardNav.ts",
"packages/studio/src/player/components/Timeline.tsx",
"packages/studio/src/player/components/TimelineCanvas.tsx",
"packages/studio/src/player/components/TimelineClip.tsx",
"packages/studio/src/player/components/TimelineClipDiamonds.tsx",
// Sub-composition html/body scoping fix: these files carry pre-existing
// CRITICAL/HIGH functions (inlineSubCompositions, bundleToSingleHtml,
// mountCompositionContent/loadExternalCompositions, scopeSelector/
// replaceAuthoredRootIdSelectors) that pre-date this PR. The change only
// threads a scopeRootSelectors option through the existing scoping calls;
// the line-shift fingerprint re-flags the inherited complexity.
"packages/core/src/compiler/compositionScoping.ts",
"packages/core/src/compiler/inlineSubCompositions.ts",
"packages/core/src/compiler/htmlBundler.ts",
"packages/core/src/runtime/compositionLoader.ts",
],
},
}