Files
hyperframes/packages/studio/src/components/editor/propertyPanelFlatClosedGroup.tsx
T
Vance Ingalls a4b3eb0b9e fix(studio): make the reveal actually fire, and give a hidden group a way back
Review findings 8, 12, 13.

**12 — the reveal was dead in BOTH halves,** which is why nobody caught it: each
half hid the other.

- *Not already selected.* `openClipFxRack` raises the request and then selects
  the clip asynchronously, so the selection landed AFTER and
  `setSelectedElementId` cleared `revealedAudioFxTarget` — the very request that
  caused the selection. The clear now spares a request whose `elementKey` is the
  element being selected; any other selection still drops it, because a request
  aimed elsewhere is stale.
- *Already selected.* `FxSection` consumed with
  `useState(revealTarget ?? null)`, so `consumedReveal` initialised EQUAL to the
  request and the `!==` never fired — and a second click on the same lane was
  byte-identical, so also inert. It now consumes by nonce, initialised null,
  which is what `PropertyPanelFlat` already does for the same hazard and for the
  same reason. `propertyPanelAudioFxGroup` forwarded `automationTarget` and
  dropped the nonce; it forwards both now.

**13 — an unescapable node id took the panel down instead of failing quietly.**
`revealRowSelector` interpolated chain strings straight into `querySelector`.
`parseAudioFxNode` accepts any non-empty string as an id and
`parseAutomationTarget` only splits on `.`, so a hand- or LLM-authored chain can
carry `a"]` — and the throw escaped a render-phase effect. Now escaped with the
repo's own `escapeCssString` (which `findElementForTimelineElement` uses for
exactly this) and wrapped, so a malformed selector returns false, which is the
documented "row not mounted yet" contract.

**8 — a hidden group had no way back.** The panel's visibility toggle is
withheld for any audio selection, and the group header carries no visibility
control now that mute and solo are gone — so a `data-hidden` bus was silent in
preview (the bus's mute gain) and absent from the render (every member dropped),
recoverable only by hand-editing the HTML. It is now offered while hidden, the
same door-from-the-inside `TimelineTrackPlainHeader` keeps for an audio TRACK
after the identical trap was diagnosed there on this branch.

That fix needed a second one to work: `selectedElementHidden` derives from
`timelineElements`, and after 0e86e64d2 a bus is not one — no timeline row, no
`hidden` flag. `isSelectionHidden` falls back to the element's own attribute.

`clearRevealedAudioFxTarget` is now called on unmount, closing a "left open"
item in the PR body. The whole reveal consumption moved to
`useAudioFxRevealSection.ts` because PropertyPanelFlat crossed 600 — the hook
holds all three hazards (currency, nonce, retirement) instead of restating them
at the call site.

studio: 103 editor files, 1276 tests. fallow clean.
2026-08-20 16:41:21 -07:00

47 lines
1.7 KiB
TypeScript

/**
* A collapsed group's header row in the flat inspector.
*
* Its own module so `PropertyPanelFlat.tsx` stays under the studio's 600-line
* cap; it reads only its arguments, which is what makes it separable.
*/
import { DesignPanelInputProvider } from "../../contexts/DesignPanelInputContext";
import { slugifyDesignInput } from "../../utils/designInputTracking";
import { FlatGroupHeader } from "./propertyPanelFlatPrimitives";
import type { FlatGroupDescriptor } from "./propertyPanelFlatDescriptors";
/** Its title, its one-line summary, and the entrance animation only the group
* just toggled gets. */
export function closedGroupHeader(
group: FlatGroupDescriptor,
toggleOpen: (id: string) => void,
justToggledIds: readonly string[],
) {
return (
<DesignPanelInputProvider key={group.id} section={slugifyDesignInput(group.title)}>
<FlatGroupHeader
title={group.title}
isOpen={false}
onToggleOpen={() => toggleOpen(group.id)}
summary={group.summary}
animateEntrance={justToggledIds.includes(group.id)}
/>
</DesignPanelInputProvider>
);
}
/**
* Is the selection hidden RIGHT NOW.
*
* `selectedElementHidden` is derived from `timelineElements`, and an
* `<hf-audio-group>` is not one — it is a mixer bus, and the runtime no longer
* stamps timing on it, so it has no timeline row to carry a `hidden` flag. Its
* `data-hidden` lives only on the element, so the attribute is the fallback.
*/
export function isSelectionHidden(
fromTimeline: boolean,
element: { dataAttributes?: Record<string, string | undefined> } | null | undefined,
): boolean {
return fromTimeline || element?.dataAttributes?.["hidden"] != null;
}