Skip to content

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

Merged
vanceingalls merged 5 commits into
mainfrom
08-22-fix_studio_give_the_group_row_s_caret_the_panel_s_glyph_and_size_back
Aug 22, 2026
Merged

fix(studio): give the group row's caret the panel's glyph and size back#3415
vanceingalls merged 5 commits into
mainfrom
08-22-fix_studio_give_the_group_row_s_caret_the_panel_s_glyph_and_size_back

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

Stacked on #3414. Part of restoring fixes stranded in the unreviewed #3363 — see #3413 for the full context.

What

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 swap between and in font-mono:

  • propertyPanelFxPresetRun.tsx (hf-fx-preset-run-caret) — collapsed ? "▸" : "▾"
  • propertyPanelFxNodeOpenBody.tsxdetails ? "▾" : "▸"

So the same affordance rendered 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.

Note the size is the value the original fix chose; hf-fx-preset-run-caret has no CSS rule of its own (it inherits), so "13px matches the panel exactly" is not something I measured — the mono + swap consistency is.

Tests

Three, mounting the header — there was no test file for this component at all, which is how it silently reverted:

  • the glyph swaps rather than rotating
  • no rotate transform on the glyph
  • the mono class is present and the smaller text-[11px] is gone

Verified all three fail against the previous caret.

Checks

tsc --noEmit clean · new tests 3/3 · oxlint / oxfmt clean · fallow audit no issues · all touched files well under the studio 600-LOC limit.

🤖 Generated with Claude Code

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First review, no prior reviews or comments. Read both files plus the two cited panel carets at 26f3bf9835.

Strengths

The comparison targets are exactly as described. propertyPanelFxPresetRun.tsx:165-166 is hf-fx-preset-run-caret, font-mono, collapsed ? "▸" : "▾"; propertyPanelFxNodeOpenBody.tsx:244 is details ? "▾" : "▸" under a font-mono parent. Swap-rather-than-rotate is the right call and the reasoning behind it is correct — is not square, so rotating it shifts the optical centre inside a fixed 24px box.

The body declines a claim it cannot back, and it is right to. hf-fx-preset-run-caret genuinely has no CSS rule of its own — the only two references in the tree are the className itself and this PR's new comment — and its parent hf-fx-preset-run-label (:150) sets no size either, so that caret's rendered size really is unmeasured. Saying so beats asserting a match.

The new test's caret selector is unambiguous, which is easy to get wrong in a header with four buttons: only the disclosure button's aria-label contains "tracks" (TimelineGroupHeader.tsx:78); the lane toggle says "lanes" (:161).

Nits

The new code comment claims more than the body does, and the comment is the part that stays in the tree. :79-81 says "matching the property panel's disclosure carets (hf-fx-preset-run-caret, propertyPanelFxNodeOpenBody) — the same affordance should not be smaller here than it is there." But propertyPanelFxNodeOpenBody's caret sits under text-[9px] (:240), so at text-[13px] this one is now materially larger than that one, and "should not be smaller" is inverted for it. The body is careful about precisely this point; the comment should carry the same care. Narrowing it to the two things that are true — mono, and swapped rather than rotated — costs nothing and keeps the next reader from re-deriving a size match that was never measured.

The third test's name outruns its assertions. it("matches the property panel's carets: mono, and not the smaller 11px") reads only this component's own className: font-mono present, text-[13px] present, text-[11px] absent. Nothing in it looks at either panel caret, so if the panel's carets move, the test stays green while the invariant in its name is broken. Either rename it to what it actually pins, or assert against the panel caret if the cross-component match is the thing worth guarding. This matters a little more than usual given the file's stated purpose is to stop a silent revert: a test whose name overstates its reach is how the next person concludes the invariant is covered when it is not.

Important

This rung ran no test job, so the three new tests have never executed in CI. The full check list is 13 entries with no Test, Build, Typecheck, Lint or Format; #3413, which targets main, gets 45 including all of those. (regression reads pass while regression-shards reads skipping — the aggregate is passing over shards that never ran.) That is the stack's base and path gating rather than anything you did, but it lands oddly here specifically: TimelineGroupHeader.test.tsx is a brand-new file whose entire reason for existing is that this fix reverted once with no test watching it, and right now it is green only on your machine. Worth getting it to run once on a main-targeting rung before this lands.

Verdict: COMMENT
Reasoning: The change is small and correct and its comparison targets check out, with two claims — one in a comment, one in a test name — that reach past what was actually verified, plus the CI gap that leaves the new tests unexecuted.

— Rames Jusso

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strengths: TimelineGroupHeader.tsx:77-94 replaces the transform with explicit collapsed/expanded glyphs, preserves the existing accessible button state, and inherits the mono sizing at the actual disclosure control. TimelineGroupHeader.test.tsx:57-72 pins the glyph swap, absence of rotation, and typography regression independently.

Stack note: This approval is the code verdict for exact head 26f3bf98; #3413/#3414 carry separate blockers and the bottom rung carries the full stack gate.

Verdict: APPROVE
Reasoning: The caret restoration is scoped, accessible, and directly regression-tested with no separate correctness issue found.

— Magi

@vanceingalls
vanceingalls force-pushed the 08-22-fix_studio_put_the_timeline_s_portaled_surfaces_on_the_tier_the_other_portals_use branch from e90f3c2 to 6a42079 Compare August 22, 2026 11:17
@vanceingalls
vanceingalls force-pushed the 08-22-fix_studio_give_the_group_row_s_caret_the_panel_s_glyph_and_size_back branch from 26f3bf9 to 27b1faa Compare August 22, 2026 11:17
@vanceingalls

Copy link
Copy Markdown
Collaborator Author

Both nits fixed at 27b1faa0 — no behaviour change.

The comment. It claimed the size matched the panel's carets and that "the same affordance should not be smaller here than it is there", which is inverted for one of the two: propertyPanelFxNodeOpenBody.tsx:240 sits under text-[9px], so at 13px this one is materially larger, and hf-fx-preset-run-caret has no size rule at all. Narrowed to what holds — mono, and the size being this row's own call with no measured target to match:

// Mono, like both of the property panel's disclosure carets. The size is
// this row's own call and not a match to either of them: the node-body
// caret sits under `text-[9px]`, and `hf-fx-preset-run-caret` has no
// size rule at all, so there is no measured target to match.

The test name. Renamed to what it actually pins — keeps the caret mono at 13px, not back down at the 11px the row inherits — with a line saying the panel carets are not read here and no cross-component match is claimed. Left as a same-component assertion rather than reaching into the panel: the invariant worth guarding against the revert is this row not silently going back to the inherited 11px, and asserting against a caret whose size is unmeasured would pin nothing.

3 tests pass; full studio suite 4378 passing on this tip.

The CI gap is real and unchanged — this rung is base/path-gated, #3413 carries the main-targeting jobs.

miguel-heygen
miguel-heygen previously approved these changes Aug 22, 2026

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strengths: TimelineGroupHeader.tsx:77-94 still carries the correct behavioral restoration, and the follow-up now states only what is measured: mono, explicit 13px, and swapped glyphs rather than a cross-component size match. TimelineGroupHeader.test.tsx:67-74 was renamed to the same honest invariant.

I closed the stacked-CI gap locally at exact tip 27b1faa0: after building the required workspace packages, the new header test plus the affected popover/context-menu tests pass 29/29.

Verdict: APPROVE
Reasoning: The prior approval still holds, the overclaiming comment/test name are corrected, and the previously unexecuted caret regression now passes on the exact restacked tip.

— Magi

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at 27b1faa046. My one finding is FIXED, and fixed the honest way.

The test is now it("keeps the caret mono at 13px, not back down at the 11px the row inherits") with a comment saying outright that it reads only this component's className and does not claim a match against the panel carets. The source comment at :79-82 was corrected the same way — it now says the size is this row's own call, and names why there is nothing to match: the node-body caret sits under a text-[9px] parent and hf-fx-preset-run-caret carries no size rule at all. That is exactly the state of those two files, so the comment is now checkable instead of aspirational. Renaming rather than reaching across components was the right pick for a test whose job is to stop a silent revert.

The CI gap I flagged is unchanged, and it is the only thing left here. At this head:

  • 13 check-runs (vs 45 on #3413, which targets main). No Test, no Build, no Typecheck.
  • regression = success while regression-shards = skipped, and Perf: ${{ matrix.shard }} = skipped. The green is vacuous — it is a gate passing over shards that never ran.

So the three revert-protection tests in TimelineGroupHeader.test.tsx still have not executed once, on the PR whose stated purpose is to stop this fix from being reverted again.

Correcting my own framing from the last round, because I told Vance in Slack that #3415 "is the one that can land" and that is looser than the facts: this PR's base is 08-22-fix_studio_put_the_timeline_s_portaled_surfaces_on_the_tier_the_other_portals_use, i.e. #3414's branch — not main. Merging it moves it into its parent branch, so it cannot reach main ahead of #3413 and #3414, and APPROVED/CLEAN here is scoped to that parent, not to trunk. The practical ask is narrower than "don't merge": before the stack's last rung merges into main, make sure one full-matrix run has actually covered these files — which happens naturally if each rung is re-targeted to main as its parent lands, and does not happen if the stack collapses without any rung sitting on main with these files in it.

Verdict: COMMENT
Reasoning: The finding is resolved and the comment is now accurate; the untested-tests concern is structural CI behaviour rather than anything wrong in this diff, and my earlier "can land" phrasing overstated the risk.

— Rames Jusso

vanceingalls and others added 2 commits August 22, 2026 04:41
…er 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.
…me 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>
@vanceingalls
vanceingalls force-pushed the 08-22-fix_studio_put_the_timeline_s_portaled_surfaces_on_the_tier_the_other_portals_use branch 2 times, most recently from 949e123 to 4fa063d Compare August 22, 2026 11:44
@vanceingalls
vanceingalls force-pushed the 08-22-fix_studio_give_the_group_row_s_caret_the_panel_s_glyph_and_size_back branch from 27b1faa to 7d85911 Compare August 22, 2026 11:44
`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>
@vanceingalls
vanceingalls force-pushed the 08-22-fix_studio_give_the_group_row_s_caret_the_panel_s_glyph_and_size_back branch from 7d85911 to 9bb45b9 Compare August 22, 2026 11:45
vanceingalls and others added 2 commits August 22, 2026 04:46
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.
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>
@vanceingalls
vanceingalls force-pushed the 08-22-fix_studio_give_the_group_row_s_caret_the_panel_s_glyph_and_size_back branch from 9bb45b9 to 3dcb44c Compare August 22, 2026 11:46
@vanceingalls

Copy link
Copy Markdown
Collaborator Author

Restack note, not a code change: #3413 merged as 277fe0fa while this was in flight, so both branches were rebased onto the post-merge main. Rames' comment-interval nit from #3413 (y = 8..40y = 8..168 in TimelineFxPopover.test.tsx) missed that merge, so it now rides here as 836922a1 — the merged PR's assertions were already correct, only the comment interval was wrong.

Heads: #3414 836922a1, #3415 3dcb44cf (restacked onto #3414's current tip). Targeted studio tests pass at both.

Base automatically changed from 08-22-fix_studio_put_the_timeline_s_portaled_surfaces_on_the_tier_the_other_portals_use to main August 22, 2026 12:01
@vanceingalls
vanceingalls dismissed miguel-heygen’s stale review August 22, 2026 12:01

The base branch was changed.

@vanceingalls
vanceingalls merged commit c594023 into main Aug 22, 2026
45 checks passed
@vanceingalls
vanceingalls deleted the 08-22-fix_studio_give_the_group_row_s_caret_the_panel_s_glyph_and_size_back branch August 22, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants