mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-14 21:29:34 +00:00
refactor: derive the dataset keys for the FX and automation attributes
The studio writes these attributes through `HF_AUDIO_FX_ATTR` and `HF_AUDIO_AUTOMATION_ATTR` but reads them out of `dataAttributes`, which is keyed without the `data-` prefix — so the read side carried its own `"fx-chain"` and `"automation"` literals in four places. The two spellings had no link, so a rename could only half-land: the writes would move and the panel would go on reading an attribute nothing writes any more. Both keys are now derived from their attribute (`HF_AUDIO_FX_DATA_KEY`, `HF_AUDIO_AUTOMATION_DATA_KEY`) and the four read sites use them. Falsified two ways. Repointing the derived key breaks 24 existing panel and summary tests, which is what proves the reads actually go through it. And the new one-line pairing assertion in each module fails the moment the derivation is replaced by a literal again — which is the regression it exists for, since a hardcoded key that happens to be right today passes every other test in the suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ce7c9e4867
commit
5a22474357
@@ -3,6 +3,8 @@ import {
|
||||
applyCurve,
|
||||
shapeProgress,
|
||||
fxAutomationTarget,
|
||||
HF_AUDIO_AUTOMATION_ATTR,
|
||||
HF_AUDIO_AUTOMATION_DATA_KEY,
|
||||
isConstantLane,
|
||||
parseAutomation,
|
||||
parseAutomationTarget,
|
||||
@@ -30,6 +32,14 @@ const lane = (points: HfAutomationLane["points"], target = "volume"): HfAutomati
|
||||
points,
|
||||
});
|
||||
|
||||
describe("the automation attribute's two spellings", () => {
|
||||
it("names the same attribute either way", () => {
|
||||
// Same split as the FX chain: written as an attribute, read as a dataset
|
||||
// key. Derived, so a rename cannot half-land.
|
||||
expect(HF_AUDIO_AUTOMATION_ATTR).toBe(`data-${HF_AUDIO_AUTOMATION_DATA_KEY}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("targets", () => {
|
||||
it("reads volume and fx targets, and rejects anything else", () => {
|
||||
expect(parseAutomationTarget("volume")).toEqual({ kind: "volume" });
|
||||
|
||||
@@ -15,6 +15,9 @@ import { getAudioFxDef, type HfAudioFxChain } from "./audioFx.js";
|
||||
|
||||
export const HF_AUDIO_AUTOMATION_ATTR = "data-automation";
|
||||
|
||||
/** The same attribute as a `dataset` / `dataAttributes` key. See `HF_AUDIO_FX_DATA_KEY`. */
|
||||
export const HF_AUDIO_AUTOMATION_DATA_KEY = HF_AUDIO_AUTOMATION_ATTR.slice("data-".length);
|
||||
|
||||
/** Automation files are versioned; a reader must refuse a version it doesn't know. */
|
||||
export const HF_AUDIO_AUTOMATION_VERSION = 1;
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
enabledAudioFxNodes,
|
||||
getAudioFxDef,
|
||||
HF_AUDIO_FX,
|
||||
HF_AUDIO_FX_ATTR,
|
||||
HF_AUDIO_FX_CHAIN_VERSION,
|
||||
HF_AUDIO_FX_DATA_KEY,
|
||||
HF_AUDIO_FX_IDS,
|
||||
normalizeAudioFxParams,
|
||||
parseAudioFxChain,
|
||||
@@ -47,6 +49,15 @@ describe("effect registry", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("the chain attribute's two spellings", () => {
|
||||
it("names the same attribute either way", () => {
|
||||
// The studio WRITES through the attribute and READS through the dataset
|
||||
// key, so the read side used to carry its own `"fx-chain"` literal and a
|
||||
// rename would only half-land. Derived now; this is what keeps it derived.
|
||||
expect(HF_AUDIO_FX_ATTR).toBe(`data-${HF_AUDIO_FX_DATA_KEY}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeAudioFxParams", () => {
|
||||
it("fills missing keys with defaults", () => {
|
||||
expect(normalizeAudioFxParams("peaking", {})).toEqual(defaultAudioFxParams("peaking"));
|
||||
|
||||
@@ -16,6 +16,16 @@
|
||||
|
||||
export const HF_AUDIO_FX_ATTR = "data-fx-chain";
|
||||
|
||||
/**
|
||||
* The same attribute as a `dataset` / `dataAttributes` key — the `data-` prefix
|
||||
* is not part of that spelling.
|
||||
*
|
||||
* Derived rather than restated: the studio writes through
|
||||
* `HF_AUDIO_FX_ATTR` and reads through the key, so a hardcoded `"fx-chain"`
|
||||
* on the read side is a rename waiting to half-land.
|
||||
*/
|
||||
export const HF_AUDIO_FX_DATA_KEY = HF_AUDIO_FX_ATTR.slice("data-".length);
|
||||
|
||||
/** Chain files are versioned; a reader must refuse a version it doesn't know. */
|
||||
export const HF_AUDIO_FX_CHAIN_VERSION = 1;
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
* grouping exists to prevent — that they are seven things to manage.
|
||||
*/
|
||||
|
||||
import { parseAudioFxChain } from "@hyperframes/core/audio-fx";
|
||||
import { HF_AUDIO_FX_DATA_KEY, parseAudioFxChain } from "@hyperframes/core/audio-fx";
|
||||
import type { DomEditSelection } from "./domEditingTypes";
|
||||
|
||||
export function audioFxSummary(element: DomEditSelection): string {
|
||||
const raw = element.dataAttributes?.["fx-chain"];
|
||||
const raw = element.dataAttributes?.[HF_AUDIO_FX_DATA_KEY];
|
||||
const carveAttr = element.dataAttributes?.["fx-carve"];
|
||||
let handBuilt = 0;
|
||||
let carveNodes = 0;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useEffect, useState } from "react";
|
||||
import {
|
||||
defaultAudioFxParams,
|
||||
HF_AUDIO_FX_ATTR,
|
||||
HF_AUDIO_FX_DATA_KEY,
|
||||
mintAudioFxNodeId,
|
||||
parseAudioFxChain,
|
||||
serializeAudioFxChain,
|
||||
@@ -41,6 +42,7 @@ import {
|
||||
automatedTargetsOf,
|
||||
automationAttrValue,
|
||||
HF_AUDIO_AUTOMATION_ATTR,
|
||||
HF_AUDIO_AUTOMATION_DATA_KEY,
|
||||
readPanelAutomation,
|
||||
resolveAutomationRange,
|
||||
withoutLane,
|
||||
@@ -109,7 +111,7 @@ export function AudioFxGroup({
|
||||
onSetAttributeLive: (attr: string, value: string | null) => void | Promise<void>;
|
||||
}) {
|
||||
const chain = ((): HfAudioFxChain => {
|
||||
const raw = element.dataAttributes?.["fx-chain"];
|
||||
const raw = element.dataAttributes?.[HF_AUDIO_FX_DATA_KEY];
|
||||
if (!raw) return { version: 1, nodes: [] };
|
||||
try {
|
||||
return parseAudioFxChain(raw);
|
||||
@@ -120,7 +122,10 @@ export function AudioFxGroup({
|
||||
}
|
||||
})();
|
||||
|
||||
const automation = readPanelAutomation(element.dataAttributes?.["automation"], chain);
|
||||
const automation = readPanelAutomation(
|
||||
element.dataAttributes?.[HF_AUDIO_AUTOMATION_DATA_KEY],
|
||||
chain,
|
||||
);
|
||||
const automatedTargets = automatedTargetsOf(automation);
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import {
|
||||
HF_AUDIO_AUTOMATION_ATTR,
|
||||
HF_AUDIO_AUTOMATION_DATA_KEY,
|
||||
parseAutomation,
|
||||
resolveAutomation,
|
||||
resolveAutomationRange,
|
||||
@@ -77,4 +78,4 @@ export function automationAttrValue(automation: HfAutomation): string {
|
||||
return automation.lanes.length > 0 ? serializeAutomation(automation) : "";
|
||||
}
|
||||
|
||||
export { HF_AUDIO_AUTOMATION_ATTR, resolveAutomationRange };
|
||||
export { HF_AUDIO_AUTOMATION_ATTR, HF_AUDIO_AUTOMATION_DATA_KEY, resolveAutomationRange };
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* rather than a second interpretation of the attribute.
|
||||
*/
|
||||
|
||||
import { VOLUME_TARGET } from "@hyperframes/core/audio-automation";
|
||||
import { HF_AUDIO_AUTOMATION_DATA_KEY, VOLUME_TARGET } from "@hyperframes/core/audio-automation";
|
||||
import type { DomEditSelection } from "./domEditingTypes";
|
||||
import {
|
||||
automationAttrValue,
|
||||
@@ -28,7 +28,10 @@ export function useVolumeAutomation(
|
||||
): VolumeAutomationBinding {
|
||||
// The chain is not needed to resolve a volume lane — volume is always a valid
|
||||
// target — so this deliberately does not parse it.
|
||||
const automation = readPanelAutomation(element.dataAttributes?.["automation"], undefined);
|
||||
const automation = readPanelAutomation(
|
||||
element.dataAttributes?.[HF_AUDIO_AUTOMATION_DATA_KEY],
|
||||
undefined,
|
||||
);
|
||||
const write = (next: Parameters<typeof automationAttrValue>[0]): void => {
|
||||
// Quiet: clicking the toggle used to reload the preview and restart every
|
||||
// playing track, while the same click on an effect parameter did not.
|
||||
|
||||
Reference in New Issue
Block a user