mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
* fix(slideshow): harden media controls in present decks
* refactor(slideshow): clear Fallow audit findings
Decompose flagged high-CRAP functions and extract production-code
duplications so the audit gate clears.
- core/runtime/bridge.ts handler — replace the 14-branch if-chain with a
CONTROL_HANDLERS dispatch table; flash-elements payload handling moves
to its own helper. Behavior preserved (all existing bridge.test.ts
cases hit the same dispatchers via the public installRuntimeControlBridge
API).
- player/slideshow/SlideshowController syncTo — split into
isValidSyncTarget / isCrossSlide / rerootStackTo helpers. The
stopSlideMedia decision and the stack re-rooting are now individually
named; the public method is a 4-line orchestrator.
- cli/commands/validate.ts run — extract emitJsonReport / emitTextReport
so the orchestrator no longer carries the dual JSON/text branches.
Cuts the cyclomatic complexity flagged by fallow after the
shouldIgnoreRequestFailure signature expansion shifted the fingerprint.
- player/hyperframes-player.ts — _setIframeMediaMuted and _stopIframeMedia
shared a `try { iframeDoc = contentDocument } catch { return }` preamble
(clone group 15). Extract _getSameOriginIframeDocument(): Document | null
and have both call sites consume it.
- studio/panels/SlideshowPanel.tsx — the notes controller's debounce-tail
and explicit flush() shared the pending-drain pattern (clone group 16).
Extract a drainPending() closure both call.
- player/hyperframes-player.test.ts — collapse the new stopMedia / muted
tests' repeated Object.defineProperty(iframe, "contentDocument", { get })
shape behind a stubIframeContentDocument helper.
No behavior changes — refactor only. Existing tests cover the affected
paths unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(validate): split run further; ignore test dup parity
Second Fallow pass surfaced two minor follow-ups after the first cut:
- packages/cli/src/commands/validate.ts run + emitTextReport still
carried minor CRAP findings (43.1 / 37.1, threshold 30). Extract
printValidationResult / formatConsoleEntry / formatTotals /
emitFailureReport so run becomes a try/catch + delegation, well
below the threshold; emitTextReport drops the inline format loops.
- .fallowrc.jsonc duplicates.ignore: add hyperframes-player.test.ts
alongside the existing SlideshowPanel.test.ts entry. Same reasoning
documented there — parallel arrange/act/assert test cases are
intentionally self-contained for readability; collapsing them under
shared fixtures would couple unrelated scenarios (same-origin vs
realm media, audio-locked permutations, seek bridge variants).
No behavior changes — refactor + config-policy parity only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
55 lines
2.1 KiB
TypeScript
55 lines
2.1 KiB
TypeScript
/**
|
|
* Types and type-guards for the two playback adapter paths the player supports:
|
|
*
|
|
* - `RuntimeDurationAdapter` — the HyperFrames runtime exposes `window.__player`
|
|
* with a `getDuration()` method. This is the standard path for compositions
|
|
* served through the runtime bridge.
|
|
*
|
|
* - `DirectTimelineAdapter` — same-origin standalone compositions can expose
|
|
* their GSAP master timeline at `window.__timelines` without installing the
|
|
* full runtime. The player drives play/pause/seek directly against the
|
|
* timeline object, bypassing the postMessage bridge.
|
|
*
|
|
* `PlaybackDurationAdapter` is the discriminated union the probe interval
|
|
* returns after deciding which path is available.
|
|
*/
|
|
|
|
export interface RuntimeDurationAdapter {
|
|
getDuration: () => number;
|
|
}
|
|
|
|
export interface DirectTimelineAdapter {
|
|
duration: () => number;
|
|
time: () => number;
|
|
// suppressEvents mirrors GSAP's timeline.seek(position, suppressEvents); pass
|
|
// false to fire onUpdate (so imperative-visibility compositions repaint on seek).
|
|
seek: (timeInSeconds: number, suppressEvents?: boolean) => unknown;
|
|
play: () => unknown;
|
|
pause: () => unknown;
|
|
/** Optional: set playback rate (e.g. GSAP's timeScale). Called when the player's playbackRate changes. */
|
|
timeScale?: (scale: number) => unknown;
|
|
}
|
|
|
|
export type PlaybackDurationAdapter =
|
|
| { kind: "runtime"; getDuration: () => number }
|
|
| { kind: "direct-timeline"; timeline: DirectTimelineAdapter; getDuration: () => number };
|
|
|
|
export function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null;
|
|
}
|
|
|
|
export function isRuntimeDurationAdapter(value: unknown): value is RuntimeDurationAdapter {
|
|
return isObjectRecord(value) && typeof value.getDuration === "function";
|
|
}
|
|
|
|
export function isDirectTimelineAdapter(value: unknown): value is DirectTimelineAdapter {
|
|
return (
|
|
isObjectRecord(value) &&
|
|
typeof value.duration === "function" &&
|
|
typeof value.time === "function" &&
|
|
typeof value.seek === "function" &&
|
|
typeof value.play === "function" &&
|
|
typeof value.pause === "function"
|
|
);
|
|
}
|