Files
hyperframes/packages/studio/src
Vance IngallsandClaude Opus 5 c594023895 fix(studio): give the group row's caret the panel's glyph and size back (#3415)
* fix(studio): put the timeline's portaled surfaces on the tier the other portals use

The FX popover, the grouping dialog it swaps for, and the automation selection
menu are all portaled to `document.body`, so they land in the root stacking
context — where they sat at `z-50` while the app's own chrome occupies 60, 90,
91, 92, 94, 100 and 110, and every other portal that has to clear that chrome
(`Tooltip`, `AssetContextMenu`, `InlineTextToolbar`, `RenderQueue`) already uses
`z-[200]`. These three were the odd ones out.

Scoped honestly: the clipping in the report is fixed by the height cap in the
previous commit, which is what actually cut the popover off at the timeline
chrome. This commit is tier consistency — it removes the standing risk of a
portaled timeline surface losing to any of those seven higher tiers, rather than
a demonstrated repro. Confirm against a real window before claiming more.

* fix(studio): move the remaining body-portaled context menus to the same tier

The all-sites audit in review was right and the previous commit did half the set.
Using `createPortal(…, document.body)` as the predicate rather than the timeline
directory, four more surfaces sit in the root stacking context at `z-50` below
the seven chrome tiers (60, 90, 91, 92, 94, 100, 110):

- `player/components/ClipContextMenu.tsx:51`
- `player/components/TrackGapContextMenu.tsx:78`
- `player/components/KeyframeDiamondContextMenu.tsx:99`
- `components/editor/CanvasContextMenu.tsx:215`

The fourth is the easy one to miss — it is the only one outside
`player/components/`, so a timeline-scoped sweep finds exactly the other three.
It belongs to the same set by its own account: its className is byte-identical
to `ClipContextMenu`'s and its header comment says it mirrors that file's look,
positioning, and dismiss behaviour, portaled to `document.body`.

Two body portals deliberately left alone. `sidebar/BlocksTab.tsx:125` portals
`PromptPreviewModal`, which carries its own `z-[100]`/`z-[110]` modal tier — a
`z-` class on the portal wrapper would be dead weight. `RenderQueue.tsx:235` is
already `z-[200]`. `FileTree.tsx:336` and `FileTreeNodes.tsx:103` are `fixed
z-50` but are NOT portaled — they render inside the sidebar's own stacking
context, so the root-context argument does not reach them and raising them would
be an unrelated change.

Crossing the `z-[100]`/`z-[110]` modal backdrops is unreachable for the same
reason it was for the first three: all four dismiss on an outside pointerdown,
so the press that opens a modal closes the menu first.

`CanvasContextMenu.test.tsx:95` asserted on `.fixed.z-50` to prove the menu did
NOT render; left as-is it would have passed vacuously against any tier. Updated
to the new class so it still fails if the menu renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(studio): correct the interval in the popover in-bounds test comment

`bottom: 32` with `maxHeight: 160` in a 200px viewport puts the box at y = 8..168,
not y = 8..40 — the bottom edge sits at `innerHeight - bottom`, and the comment
read it as the height instead. The assertions below already computed the right
geometry; only the stated interval was wrong, on a regression test whose comment
is the next reader's model of what it pins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(studio): give the group row's caret the panel's glyph and size back

The timeline group row was the only disclosure caret in the studio still drawn as
a rotated 11px non-mono glyph. Both of the property panel's carets
(`hf-fx-preset-run-caret` in propertyPanelFxPresetRun, and
propertyPanelFxNodeOpenBody) swap between ▸ and ▾ in `font-mono`, so the same
affordance was rendering smaller and differently on the row than in the panel it
opens.

Now mono, a size up, and swapped rather than rotated — a rotated ▸ also sits
off-centre in its box because the glyph is not square.

Three tests, mounting the header: the swap, the absence of a rotate transform,
and the mono/size class. Verified all three fail against the previous caret.

* fix(studio): stop the caret comment and test name claiming a size match

Both reached past what was actually verified, and the comment is the part that
stays in the tree.

The comment said the caret matches the property panel's carets and "should not be
smaller here than it is there". Inverted for one of the two: the node-body caret
sits under `text-[9px]` (`propertyPanelFxNodeOpenBody.tsx:240`), so at 13px this
one is materially larger, and `hf-fx-preset-run-caret` has no size rule of its
own — its rendered size is unmeasured. Narrowed to the two claims that hold:
mono, and swapped rather than rotated.

The third test was named "matches the property panel's carets" but reads only
this component's own className, so the panel carets could move and it would stay
green. Renamed to what it pins.

No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 05:17:31 -07:00
..