Commit Graph
4 Commits
Author SHA1 Message Date
Miguel Ángel 8fd5bf8f51 fix(studio): remove Ask agent popup, fix preview selection, fix manual edits in renderer
Three interrelated studio UX and rendering fixes:

1. Remove the "Ask agent" popup that auto-triggered when clicking large
   raster elements in the preview. The modal intercepted clicks meant
   for editable elements and blocked normal selection workflow.

2. Rewrite preview click selection to respect visual stacking order.
   The previous scoring algorithm weighted DOM depth at 10,000× per
   level, causing elements inside sub-compositions to beat visually-
   on-top elements (e.g., clicking Pip Studio selected Sf Chrome
   instead). The new algorithm trusts elementsFromPoint order and only
   prefers a deeper candidate when it is a descendant of the current
   pick — never jumping to an unrelated element painted behind it.

3. Fix manual edits (resize) not surviving video rendering. The
   producer's seek-reapply script handled translate and rotation but
   was missing box-size (width/height) reapplication after each GSAP
   seek. Also added data-hf-studio-box-size to the detection list in
   htmlCompiler so the script is injected for resize-only edits.
2026-05-19 15:45:51 -04:00
James Russo 2729ee5087 refactor: delete orphan declarations flagged by fallow (#949)
* ci: run fallow audit in lefthook pre-commit

Mirrors the same `fallow audit --base ... --fail-on-issues` check that
runs in CI, but locally against HEAD so issues surface at commit time
instead of after the push round-trip.

Scoped to `packages/**` source files via the glob — non-code edits
(README, docs, top-level configs) skip the hook entirely.

Measured locally: ~5s in parallel with the existing lint/format/typecheck
checks. Doesn't extend wall-clock time because typecheck (~11s) is the
long pole, and lefthook runs commands in parallel.

The default `--gate new-only` means inherited findings don't block the
commit — same gate behavior as CI, so local pre-commit and PR audit
agree.

* refactor: delete orphan declarations flagged by fallow

After fallow's auto-fix de-exports unused symbols, oxlint surfaces them
as no-unused-vars. This PR deletes those orphan declarations outright.

Biggest cleanup: studio/src/icons/SystemIcons.tsx shrinks from 132 to 57
lines — 33 unused icon wrappers and their phosphor-icon imports deleted.

Other deletions across 14 more files covering paired getter/setters,
helper functions, dead env constants, internal components with no
callers, and cascading unused imports.

Cascade-causing files held back for follow-up PRs: renderOrchestrator
barrel of captureCost re-exports, telemetry/portUtils/remote barrels,
Button.tsx + ui/index.ts (would orphan whole file), studioMotion
type re-exports.

Test plan: typecheck clean across 8 packages, oxlint + oxfmt clean,
fallow audit exit 0 (remaining findings inherited), cli + studio
vitest suites pass.
2026-05-18 21:11:03 -07:00
Miguel Ángel 225010800a feat(studio): persist element positions in HTML, fix resize overlay drift and GSAP double-translation (#829)
* feat(studio): add pasteboard background to preview viewport

Adds bg-neutral-800 to the preview viewport so the area outside the
canvas is visually distinct from the composition content — consistent
with professional video editors (Premiere, DaVinci, Figma).

* feat(studio): pasteboard background and canvas outline around preview

- NLEPreview: viewport gets bg-neutral-700 (#404040) as the pasteboard
  color surrounding the canvas — distinct from the app chrome (#0a0a0a)
- Player wrapper: drop bg-black so the pasteboard shows around the canvas
  (loading overlays still cover the area with bg-black during load)
- Player: set host background to transparent via inline style (overrides
  :host { background: #000 } in shadow DOM), and inject a style rule into
  the open shadow root so .hfp-container has overflow:visible and the
  canvas iframe gets a thin white ring + soft drop-shadow — making the
  canvas boundary legible against the pasteboard

* feat(studio): disable manual positioning JSON by default, add toggle

Manual edits were always stored in `.hyperframes/studio-manual-edits.json`,
making it hard to share source without the sidecar file and easy to
accidentally reposition elements via drag.

Changes:
- `enabled` field added to `StudioManualEditManifest` (defaults to `false`
  when absent — existing projects are unaffected until they opt in)
- Drag handles, resize, and rotation handles are hidden when disabled
- Layout X/Y/W/H/R fields in the Design panel are read-only when disabled
- "Manual positioning" toggle added at the bottom of the Design panel,
  visible whether or not an element is selected
- Toggle state is persisted to `.hyperframes/studio-manual-edits.json`
  so each project can opt in independently
- `STUDIO_PREVIEW_MANUAL_EDITING_ENABLED` env flag still acts as a hard
  cap (env off → feature off regardless of project setting)

* feat(studio): enable manual positioning by default (opt-out)

* feat(studio): allow absolute elements to drag without toggle; gate JSON-backed drag behind toggle

* feat(studio): persist positions directly to HTML; remove JSON sidecar and manual positioning toggle

Replace the `.hyperframes/studio-manual-edits.json` sidecar with inline-style
persistence baked directly into the HTML source. Drag/resize/rotation values
are written as CSS custom properties (`--hf-studio-offset-x/y`, `--hf-studio-width/height`,
`--hf-studio-rotation`) plus `translate`/`width`/`height`/`rotate` inline styles via
`persistDomEditOperations` — no re-apply step needed on load.

Key changes:
- `sourcePatcher`: add `value: string | null` to `PatchOperation` — null removes the
  property/attribute from the HTML tag instead of setting it
- `manualEditsDom`: add `build*Patches` / `buildClear*Patches` helpers that capture live
  element state into `PatchOperation[]` for HTML source writes; add
  `reapplyPositionEditsAfterSeek` (DOM-query-based seek hook, queries data-attribute markers)
- `manualEdits.ts`: remove `applyStudioManualEditManifest` and all manifest target
  resolution; export `reapplyPositionEditsAfterSeek`; keep seek/play wrap infrastructure
- `useManifestPersistence`: remove all JSON I/O — no disk read on load, no manifest
  state, no toggle state; `applyCurrentStudioManualEditsToPreview` now only installs
  seek hooks via `reapplyPositionEditsAfterSeek`
- `useDomEditCommits`: replace `commitStudioManualEditManifestOptimistically` calls with
  direct DOM apply + `commitPositionPatchToHtml` (queued HTML patch write, skipRefresh)
- `DomEditOverlay`: remove `manualEditsEnabled` prop; revert all `canMove || manualEditsEnabled`
  gates to just `canApplyManualOffset` — every draggable element is always draggable
- `PropertyPanel`: remove `ManualPositioningToggle` component and all toggle props
- `manualEditsParsing/manualEditsTypes`: remove manifest types, upsert functions, and
  `STUDIO_MANUAL_EDITS_PATH`; keep `finiteNumber`, `readStudioFileChangePath`,
  `roundRotationAngle`, and snapshot/CSS-property types

* fix(studio): sync keyboard shortcut handler with main; fix keepPlaying seek assertions in test

* fix(studio): strip GSAP-cached translate from transform on path offset apply

* fix(studio): remove Reset edits button from design panel

* feat(studio): wire reloadPreview into manifest persistence; drop stale group-selection refresh

- Pass `reloadPreview` into `useManifestPersistence` so undo/redo reloads
  via the refresh-key path instead of directly touching the iframe.
- Remove `refreshDomEditGroupSelectionsFromPreview` from commit handlers;
  HTML is now the source of truth so no stale-ref refresh is needed.
- Add `manualEditsRenderScript` helper; export via studio-api and apply
  it in `htmlCompiler` during HTML compilation.

* fix(studio): prevent root composition from being selected; correct overlay drift on resize

- Guard `getDomLayerPatchTarget` against elements with `data-composition-id`
  so the root composition div is never returned as a visual selection target.
- Apply the same guard to the raw `elementFromPoint` fallback in
  `getPreviewTargetFromPointer`, which was the actual escape path.
- Thread `iframeRef` into gesture handler opts; after applying draft
  dimensions during resize, re-read the element BCR via `toOverlayRect`
  and update the overlay box position to compensate for visual drift on
  elements with centered transform-origin (e.g. GSAP scale tweens).

* fix(studio): correct resize overlay for scaled elements; block invisible element selection

- Resize: use BCR from `toOverlayRect` for both position and size after
  applying draft dimensions — GSAP scale makes visual size diverge from
  raw CSS size, BCR is the only accurate source during a gesture.
- Click selection: add `isElementComputedVisible` guard to the
  `elementFromPoint` fallback so opacity-0 / autoAlpha-hidden elements
  cannot be selected even though the browser hit-test returns them.

* fix(studio): reload preview on external file changes via SSE/HMR

Share the app-level domEditSaveTimestampRef with useManifestPersistence
so the SSE/HMR handler can suppress echoes from all studio saves (code
tab, timeline, DOM edits), then call reloadPreview() for non-motion
external changes that aren't echoes of our own saves.

* fix(studio): suppress post-resize click to keep selection on resized element

* fix(studio): serve registry blocks without index.html in preview

Blocks ship as {id}.html + assets/ with no index.html. The preview
route hard-coded index.html so these projects returned 404 and their
assets (e.g. korea-map.png, map-nyc-paris.png) were never served.

Add resolveProjectMainHtml() that falls back to {id}.html, thread the
resolved compositionPath through transformPreviewHtml and
injectStudioPreviewAugmentations, and update listProjects() in the
vite adapter to surface block directories in the project list.

* fix(render): preserve studio drag/resize/rotation offsets in rendered video

Three issues caused studio-edited positions to be lost during rendering:

1. The seek-reapply script used setInterval to wrap window.__hf.seek, but
   Puppeteer's page.evaluate() calls don't yield the event loop for
   macrotasks — the interval never fired, so reapplyAll() never ran after
   GSAP seeks. Fix: use Object.defineProperty to trap writes to the seek
   property, wrapping it synchronously the instant the bridge assigns it.

2. MEDIA_VISUAL_STYLE_PROPERTIES (copied from <video> to proxy <img>
   during render) included "transform" but not "translate", "rotate", or
   "scale" — the CSS Transforms Level 2 individual properties used by
   studio drag/resize/rotation. The proxy was positioned at offsetLeft/
   offsetTop without the translate offset.

3. getViewportMatrix (HDR compositor) only read cs.transform, missing
   individual transform properties entirely. Added composeIndividualTransforms
   to build the translate × rotate × scale matrix and compose it before
   the legacy transform matrix.

* fix(studio): select elements with pointer-events: none in preview

Compositions often set pointer-events: none on scenes, avatar wrappers,
and decorative layers. elementsFromPoint() skips these elements entirely,
making them unselectable in the Studio. Fix: temporarily inject a
* { pointer-events: auto !important } stylesheet during hit-testing, then
remove it immediately after.

Also adds a pointer_events_none lint rule (info severity, visible with
--verbose) so authors know which selectors may affect Studio selection.
2026-05-15 06:43:00 +02:00
Miguel Ángel 91bdffffe6 fix(ci): scope LOC check to studio, split useTimelinePlayer + hyperframes-player under 500 LOC (#750)
* refactor: split useTimelinePlayer.ts and hyperframes-player.ts into focused modules (<500 LOC each)

* fix(ci): scope 500 LOC check to packages/studio, add allowlist for grandfathered files

* feat(cli): Linux ARM64 support — auto-install Chromium on DGX Spark / GB10 / Jetson

Chrome Headless Shell has no Linux ARM64 binary. On arm64 Linux:
- Detects the platform automatically
- Tries to auto-install system Chromium via apt-get (works on Ubuntu/Debian ARM)
- Falls back to clear manual instructions with exact commands
- 'hyperframes browser ensure' guides through the setup interactively
- After setup, all render commands work without any flags

* fix(ci): disable Windows Defender real-time monitoring to prevent EPERM builds

Path exclusions are insufficient — Defender re-scans new files created
during bun install before the exclusion takes effect. Disable real-time
monitoring for the entire job duration instead (standard CI practice).

* refactor(studio): split all files >500 LOC + extract useToast, delete allowlist

All 11 large files split into focused modules under 500 LOC.
App.tsx extracted toast logic into useToast hook (493 LOC now).
.filesize-allowlist deleted — no longer needed.

* fix: remove unused imports from split files, extract useToast from App.tsx

App.tsx: 504 → 493 lines (toast logic extracted to useToast hook)
timelineDOM.ts: remove unused imports from re-export pattern
MotionPanel.tsx: remove unused clampStudioCustomEasePoints import
studioMotionOps.ts: remove unused StudioGsapMotionDirection import

* fix(ci): use Set-MpPreference to fully disable Windows Defender (both jobs)

* fix(producer): use node --experimental-strip-types instead of tsx for build:fonts

Eliminates the tsx binary dependency that Windows Defender locks during
bun install, causing EPERM errors. Node 22.6+ strips TypeScript types
natively with no external binary.

* chore: remove .filesize-allowlist — App.tsx is now 493 lines (<500)

* fix(ci): disable Windows Defender before checkout to prevent all EPERM races

* fix(producer): skip build:fonts if fontData.generated.ts already exists

The generated file is tracked in git, so CI doesn't need to regenerate
it. This avoids @fontsource/inter node_modules access on Windows which
triggers EPERM from Defender scanning during bun install.
2026-05-13 01:48:12 +02:00