Skip to content

fix(studio-server): previewAdapter getElementTimings ignores relative data-start refs#2097

Merged
vanceingalls merged 2 commits into
mainfrom
fix/preview-adapter-relative-timing
Jul 9, 2026
Merged

fix(studio-server): previewAdapter getElementTimings ignores relative data-start refs#2097
vanceingalls merged 2 commits into
mainfrom
fix/preview-adapter-relative-timing

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

What

createPreviewAdapter's getElementTimings() did a raw parseFloat on data-start, which can be a relative-reference expression ("intro", "intro + 2") per the docs' Relative Timing grammar, not just an absolute number. Any reference silently resolved to undefined. It also never read data-duration at all — only data-start/data-end literally — so a reference to a duration-authored (not end-authored) clip was unresolvable either way.

Why

Same bug class just fixed in @hyperframes/sdk's getElementTimings() (#2092) — found while auditing the SDK's surface, since this is studio's own (pre-SDK, server-side) copy of the same capability and had the identical unfixed bug.

How

Both fixed via the shared parseStartExpression grammar parser (@hyperframes/core/runtime/start-expression), with the same recursive resolver + cycle-guard pattern as the SDK fix (split into resolveStart/resolveReferenceStart/resolveEnd to keep each function's complexity low — fallow's complexity gate caught the unsplit version). Reference resolution reuses this file's existing findById (bare data-hf-id lookup).

Test plan

  • 6 new tests: duration-based end resolution (never worked before), relative reference with/without offset, missing target, mutual-cycle termination
  • Full existing previewAdapter.test.ts suite passes (28/28)
  • Full @hyperframes/studio-server suite passes (22 files / 256 tests)
  • bun run build clean (studio-server + full workspace, incl. studio)

@vanceingalls vanceingalls force-pushed the fix/preview-adapter-relative-timing branch 2 times, most recently from cea98f9 to 0a036dc Compare July 9, 2026 06:57
@vanceingalls vanceingalls changed the base branch from main to graphite-base/2097 July 9, 2026 06:57
@vanceingalls vanceingalls changed the base branch from graphite-base/2097 to fix/position-edits-cross-realm July 9, 2026 06:59

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good port of the SDK's #2092 resolver pattern to studio-server's previewAdapter. Root cause is correct (raw parseFloat on reference expressions + missing data-duration support), and the recursive resolver with cycle guard is well-structured.

Two non-blocking observations:

  1. SSOT documentation — This is now the third copy of the "resolve relative data-start" pattern (runtime startResolver.ts, SDK getElementTimings, studio-server previewAdapter). The runtime version is substantially more complex (host offsets, media, timelines), so a shared extraction isn't straightforward. But a fallow-ignore comment at the top of the resolver block (like positionEdits.ts does for splitTopLevelWhitespace) would help future readers understand why three resolvers coexist and which is the authority.

  2. Dead parseFloat fallback (resolveStart, the else branch) — If parseStartExpression returns null, the input is neither empty, a finite number, nor a valid reference expression. A subsequent parseFloat can only succeed on strings like "3abc" (leading digits that Number() rejects). Harmless, but if it fires it's probably a malformed attribute that should resolve to undefined anyway. Consider dropping the fallback or adding a brief comment explaining when it could fire.

Test coverage is thorough — duration-based end, reference with/without offset, missing target, and mutual cycle all covered.

LGTM.

— Miga

@james-russo-rames-d-jusso james-russo-rames-d-jusso left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed at 0a036dc.

Solid recursive resolver with the right cycle-safety shape — visiting set entered/exited in try/finally, startCache memoizing undefined (via .has-then-.get) so a broken chain doesn't re-explore on every outer-loop iteration, mutual recursion between resolveStart and resolveEnd → resolveStart that still terminates through the guard. The 5-test set covers the interesting corners (duration→end path that never worked, bare vs +offset reference, missing target undefined-not-NaN, mutual cycle terminates). Nice call-out in the cycle-test comment about the intentional divergence from the SDK's static resolver (no 0-fallback → no fabricated numbers).

Two inline comments on the resolver's edges (clamp consistency w/ #2092, fallthrough silence) — both non-blocking.

One coverage nit for the "questions" bucket: no test exercises "reference target exists but its own timing is unresolvable" (e.g. target has neither data-end nor data-duration). Test 4 ("target doesn't exist") covers the findById → null branch; the current tests don't cover the findById → target && resolveEnd(target) → undefined branch. Would return undefined correctly via the existing code, but a test would pin the behavior. Cheap to add.

CI still starting up at review time — will keep an eye if any of the studio-server suites regress.

Review by Rames D Jusso

Comment thread packages/studio-server/src/helpers/previewAdapter.ts
Comment thread packages/studio-server/src/helpers/previewAdapter.ts
vanceingalls added a commit that referenced this pull request Jul 9, 2026
…ng resolver

Documents the shared-pattern context (3rd copy of "resolve relative
data-start", after runtime startResolver.ts and the SDK's own
getElementTimings) and explains when the raw parseFloat fallback in
resolveStart's else branch can actually fire (a malformed grammar string
with a leading number). Adds a test pinning the "reference target exists
but its own timing is unresolvable" branch, which existing tests didn't
cover (only "target doesn't exist" was tested).

Cross-checked the negative-offset clamp concern raised in review: the
SDK's own resolveReferenceStart (session.ts) also clamps to
Math.max(0, ...), so this stays consistent with its sibling — no code
change needed there.
vanceingalls added a commit that referenced this pull request Jul 9, 2026
…ng resolver

Documents the shared-pattern context (3rd copy of "resolve relative
data-start", after runtime startResolver.ts and the SDK's own
getElementTimings) and explains when the raw parseFloat fallback in
resolveStart's else branch can actually fire (a malformed grammar string
with a leading number). Adds a test pinning the "reference target exists
but its own timing is unresolvable" branch, which existing tests didn't
cover (only "target doesn't exist" was tested).

Cross-checked the negative-offset clamp concern raised in review: the
SDK's own resolveReferenceStart (session.ts) also clamps to
Math.max(0, ...), so this stays consistent with its sibling — no code
change needed there.
jrusso1020
jrusso1020 previously approved these changes Jul 9, 2026

@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.

Approving — relative data-start resolver port; RDJ/Miga R1 addressed at 8fb7ff7 (added the target-exists-but-unresolvable test + the resolver-copies + parseFloat-fallthrough comments). CI clean (only Graphite mergeability_check pending = stack artifact). Layered on RDJ + Miga. — Rames Jusso

@vanceingalls vanceingalls changed the base branch from fix/position-edits-cross-realm to graphite-base/2097 July 9, 2026 18:16
… data-start refs

Same bug class as the SDK's getElementTimings fix (#2092): data-start can be a
relative-reference expression ("intro", "intro + 2"), not just an absolute
number. The old code did a raw parseFloat on it, so any reference silently
resolved to undefined instead of an actual time.

Also: this function never read data-duration at all (only data-start/data-end
literally), so a reference to a duration-authored (not end-authored) clip was
unresolvable regardless of the parseFloat bug — resolving a reference needs
the target's END, which for a duration-authored clip requires start+duration.

Both fixed together via the shared parseStartExpression grammar parser
(@hyperframes/core/runtime/start-expression), with the same cycle-guard
pattern as the SDK fix. Reference resolution against other elements is scoped
to this file's existing findById (bare data-hf-id lookup).

6 new tests: duration-based end resolution, relative reference (with and
without offset), missing target, and a mutual-cycle termination check.
…ng resolver

Documents the shared-pattern context (3rd copy of "resolve relative
data-start", after runtime startResolver.ts and the SDK's own
getElementTimings) and explains when the raw parseFloat fallback in
resolveStart's else branch can actually fire (a malformed grammar string
with a leading number). Adds a test pinning the "reference target exists
but its own timing is unresolvable" branch, which existing tests didn't
cover (only "target doesn't exist" was tested).

Cross-checked the negative-offset clamp concern raised in review: the
SDK's own resolveReferenceStart (session.ts) also clamps to
Math.max(0, ...), so this stays consistent with its sibling — no code
change needed there.
@vanceingalls vanceingalls force-pushed the fix/preview-adapter-relative-timing branch from 8fb7ff7 to e4c4d2e Compare July 9, 2026 18:29
@vanceingalls vanceingalls changed the base branch from graphite-base/2097 to main July 9, 2026 18:29
@vanceingalls vanceingalls dismissed jrusso1020’s stale review July 9, 2026 18:29

The base branch was changed.

@vanceingalls vanceingalls merged commit d32fb19 into main Jul 9, 2026
52 of 58 checks passed
@vanceingalls vanceingalls deleted the fix/preview-adapter-relative-timing branch July 9, 2026 18:46

@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-approving at head e4c4d2e1 — all 8 regression-shards green (0 failures), and the previewAdapter relative-timing fix + the R1 follow-ups (unresolvable-timing test, resolver-copies context comment, parseFloat fallthrough comment) are all present. ci.yml (Build/Lint/Typecheck) green as the stack bottom.

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.

4 participants