diff --git a/packages/core/src/audioAutomation.test.ts b/packages/core/src/audioAutomation.test.ts index 7b9af2c747..61fefbf313 100644 --- a/packages/core/src/audioAutomation.test.ts +++ b/packages/core/src/audioAutomation.test.ts @@ -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" }); diff --git a/packages/core/src/audioAutomation.ts b/packages/core/src/audioAutomation.ts index 5278cd7d1c..fe6909b719 100644 --- a/packages/core/src/audioAutomation.ts +++ b/packages/core/src/audioAutomation.ts @@ -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; diff --git a/packages/core/src/audioFx.test.ts b/packages/core/src/audioFx.test.ts index 3caaa2464c..09b60ae660 100644 --- a/packages/core/src/audioFx.test.ts +++ b/packages/core/src/audioFx.test.ts @@ -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")); diff --git a/packages/core/src/audioFx.ts b/packages/core/src/audioFx.ts index 32be62bc64..8ab6c60cc0 100644 --- a/packages/core/src/audioFx.ts +++ b/packages/core/src/audioFx.ts @@ -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; diff --git a/packages/studio/src/components/editor/audioFxSummary.ts b/packages/studio/src/components/editor/audioFxSummary.ts index 441217a8ab..a56ee5323c 100644 --- a/packages/studio/src/components/editor/audioFxSummary.ts +++ b/packages/studio/src/components/editor/audioFxSummary.ts @@ -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; diff --git a/packages/studio/src/components/editor/propertyPanelAudioFxGroup.tsx b/packages/studio/src/components/editor/propertyPanelAudioFxGroup.tsx index bcc0e16ed5..5d132620e9 100644 --- a/packages/studio/src/components/editor/propertyPanelAudioFxGroup.tsx +++ b/packages/studio/src/components/editor/propertyPanelAudioFxGroup.tsx @@ -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; }) { 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); /** diff --git a/packages/studio/src/components/editor/propertyPanelAutomation.ts b/packages/studio/src/components/editor/propertyPanelAutomation.ts index d940eedfab..3469ce29fa 100644 --- a/packages/studio/src/components/editor/propertyPanelAutomation.ts +++ b/packages/studio/src/components/editor/propertyPanelAutomation.ts @@ -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 }; diff --git a/packages/studio/src/components/editor/useVolumeAutomation.ts b/packages/studio/src/components/editor/useVolumeAutomation.ts index b0cfbb7aa1..02a2f728ab 100644 --- a/packages/studio/src/components/editor/useVolumeAutomation.ts +++ b/packages/studio/src/components/editor/useVolumeAutomation.ts @@ -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[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.