fix: add progress logging during silent render pipeline stages (#1220)

* fix: add progress logging during silent render pipeline stages

The render pipeline only updates progress at stage boundaries (5%, 10%,
25%), leaving multi-minute gaps with zero log output on low-memory
hardware. This adds log.info calls at key sub-steps within the three
silent stages:

- Probe stage (5%): browser launch, session initialization, duration
  discovery, media asset discovery, audio volume automation, video
  visibility window detection
- Video extraction (10%): per-video extraction progress
- Calibration (25%): browser launch, session initialization,
  per-frame calibration progress, final cost estimate

Also adds 30-second heartbeat timers for the two initializeSession
calls (probe and calibration) that can individually take minutes on
constrained hardware.

Closes #1218

* fix: resolve CI failures in typecheck, runtime seek test, and timeline test

- Make handleGsapMaterializeKeyframes optional in DomEditSessionSlice
  and use optional chaining at the call site (not yet wired)
- Update GSAP adapter seek test to expect nudge+seek pattern
  (totalTime with suppressEvents:true followed by actual seek)
- Fix Timeline canvas height test to use TRACK_H constant (48)
  instead of stale hardcoded value (72)

* refactor: extract helpers to meet 600-line file size limit

- App.tsx (603→594): extract StudioToast component
- useDomEditSession.ts (688→600): extract useGsapSelectionHandlers hook
- Timeline.tsx (614→557): extract useTimelineAssetDrop hook
- PropertyPanel.tsx (647→584): extract TimingSection to propertyPanelTimingSection

* style: fix formatting in TimelineToolbar
This commit is contained in:
Miguel Ángel
2026-06-05 16:17:58 -04:00
committed by GitHub
parent 20894ab9a3
commit 6bd1e764e5
14 changed files with 503 additions and 277 deletions
@@ -1,5 +1,5 @@
import { memo } from "react";
import { Clock, Eye, Layers, MessageSquare, Move, X } from "../../icons/SystemIcons";
import { Eye, Layers, MessageSquare, Move, X } from "../../icons/SystemIcons";
import { type DomEditSelection } from "./domEditing";
import { readStudioBoxSize, readStudioPathOffset, readStudioRotation } from "./manualEdits";
import type { ImportedFontAsset } from "./fontAssets";
@@ -13,6 +13,7 @@ import {
import { MetricField, Section } from "./propertyPanelPrimitives";
import { isMediaElement, MediaSection } from "./propertyPanelMediaSection";
import { TextSection, StyleSections } from "./propertyPanelSections";
import { TimingSection } from "./propertyPanelTimingSection";
import { GsapAnimationSection } from "./GsapAnimationSection";
import { KeyframeNavigation } from "./KeyframeNavigation";
import { STUDIO_GSAP_PANEL_ENABLED, STUDIO_KEYFRAMES_ENABLED } from "./manualEditingAvailability";
@@ -84,70 +85,6 @@ interface PropertyPanelProps {
onSeekToTime?: (time: number) => void;
}
/* ------------------------------------------------------------------ */
/* TimingSection */
/* ------------------------------------------------------------------ */
function formatTimingValue(seconds: number): string {
if (!Number.isFinite(seconds) || seconds < 0) return "0.00s";
return `${seconds.toFixed(2)}s`;
}
function parseTimingValue(input: string): number | null {
const cleaned = input.replace(/s$/i, "").trim();
const parsed = Number.parseFloat(cleaned);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
}
function TimingSection({
element,
onSetAttribute,
}: {
element: DomEditSelection;
onSetAttribute: (attr: string, value: string) => void | Promise<void>;
}) {
const start = Number.parseFloat(element.dataAttributes.start ?? "0") || 0;
const duration =
Number.parseFloat(
element.dataAttributes.duration ?? element.dataAttributes["hf-authored-duration"] ?? "0",
) || 0;
const end = start + duration;
const commitStart = (nextValue: string) => {
const parsed = parseTimingValue(nextValue);
if (parsed == null) return;
void onSetAttribute("start", parsed.toFixed(2));
};
const commitDuration = (nextValue: string) => {
const parsed = parseTimingValue(nextValue);
if (parsed == null || parsed <= 0) return;
void onSetAttribute("duration", parsed.toFixed(2));
};
const commitEnd = (nextValue: string) => {
const parsed = parseTimingValue(nextValue);
if (parsed == null || parsed <= start) return;
void onSetAttribute("duration", (parsed - start).toFixed(2));
};
return (
<Section title="Timing" icon={<Clock size={15} />}>
<div className={RESPONSIVE_GRID}>
<MetricField label="Start" value={formatTimingValue(start)} onCommit={commitStart} />
<MetricField label="End" value={formatTimingValue(end)} onCommit={commitEnd} />
</div>
<div className="mt-3">
<MetricField
label="Duration"
value={formatTimingValue(duration)}
onCommit={commitDuration}
/>
</div>
</Section>
);
}
/* ------------------------------------------------------------------ */
/* PropertyPanel */
/* ------------------------------------------------------------------ */