feat(sdk,studio): stage 7 step 3a — persistPath + SDK session reload-on-change#1449
feat(sdk,studio): stage 7 step 3a — persistPath + SDK session reload-on-change#1449vanceingalls wants to merge 2 commits into
Conversation
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Verified at HEAD 98d3e76.
Verdict: approve-with-concerns — clean step. The persistPath plumb through OpenCompositionOptions is well-isolated, and shouldReloadSdkSession extracted for tests is a nice shape. Reload-on-change is correctly path-gated. Couple of things worth pinning down before #1452 lands on top.
Concerns
packages/studio/src/hooks/useSdkSession.ts:36-49— reload-on-change has no idle / "is-user-mid-drag-or-playback" guard. The PR docstring says the session is "idle until Step 3c routes dispatch ops through it" — which is true today, butsetSession(null)followed by an async re-open will flipsdkSessiontonulland back during a mid-drag scenario once #1452 wires dispatch in. If an external editor/agent saves while the user is mid-drag in Studio, the session vanishes for a tick (and any in-flight SDK dispatch from #1452 throws on a null session). The docstring's "Step 3c must add self-write suppression" note covers the self-write echo, but not the external-write-during-drag case. Recommend either: (a) gate the reload behind!isDragging && !isPlaying(player store has both), or (b) keep the old session live and swap atomically once the new one opens (preserves dispatch availability through the swap). #1452's diff does add suppression but only for self-write — it doesn't address the external-write race.packages/sdk/src/session.ts:489—persistPathis read only atopenCompositiontime; once set, there's no way to change it without disposing + re-opening. That's fine for Studio's "one path per session" model, but if a future caller wants to rename the underlying file mid-session, they'll silently keep writing to the old path. Suggest a one-line note in thepersistPathJSDoc pinning the "immutable for the session lifetime" contract.packages/studio/src/hooks/useSdkSession.ts:46— SSE fallback registers a newEventSourcefor everyactiveCompPathchange but never closes it on the HMR-available path. The cleanup() => es.close()only fires from the SSE branch; on the HMR branch theesisn't constructed. That's actually correct, but the early-return shape is easy to misread. (nit — leave a// HMR path early-returns, SSE branch handles cleanupcomment, or restructure as a singleuseEffectreturning a per-branch cleanup.)
Questions
packages/studio/src/hooks/useSdkSession.ts:40— what's the source of thehf:file-changepayload shape?readStudioFileChangePath(payload)is imported from../components/editor/manualEditsbut I didn't trace what fields it reads. If it inspectspayload.pathstrictly, agent / external saves that emit a slightly different envelope (e.g. SSE wrapsMessageEvent.dataas a JSON string) will silently fail to trigger reload. Worth a smoke check with the actual SSE event shape from/api/events.
What I didn't verify
- Behaviour of
readStudioFileChangePathon realMessageEventpayloads from/api/events(SSE deliversevent.dataas a string; the handler passes the event itself aspayload, which may not match the parser's expectation). - Whether the SSE fallback ever fires in prod (HMR is only dev) — if SSE is the prod path, it's load-bearing for the reload story.
Review by Rames D Jusso
miguel-heygen
left a comment
There was a problem hiding this comment.
CI blocked by the root oxfmt failure in #1423. persistPath plumbing and reload-on-change logic are correct.
Strengths:
packages/sdk/src/session.tspersistPaththreaded throughOpenCompositionOptions→createPersistQueuecleanly. Default"composition.html"is handled at the persist-queue level; callers don't have to supply it.shouldReloadSdkSessionis a pure function extracted for testability —useSdkSession.test.tscovers all four branches (match, other-file, null comp, no path in payload). Good isolation.reloadTokenstate +setReloadToken((t) => t + 1)is the correct React pattern for triggering re-effects without adding business data to state.
Important:
-
packages/studio/src/hooks/useSdkSession.tsSSE fallback path —new EventSource("/api/events")is opened whenimport.meta.hotis falsy (production / embedded server). This is closed viaes.close()in the cleanup. However, if the component remounts rapidly (e.g.activeCompPathchanges while an SSE connection is being established), there is a window where two SSE connections may exist simultaneously until the cleanup of the first fires. This is inherently React effect behavior and is correct — just confirming the cleanup is always called. It is (return () => es.close()), so this is fine. -
packages/studio/src/hooks/useSdkSession.tsSELF_WRITE_SUPPRESS_MS = 2000is introduced in #1452, not this PR. The comment here ("Step 3c must add self-write suppression once dispatch writes") correctly documents the TODo. Confirm the suppression lands before this PR is used with the cutover path (#1452).
Verdict: APPROVE (pending #1423 oxfmt fix)
Reasoning: persistPath plumbing is minimal and correct; reload-on-change is well-tested and uses the right React pattern.
— magi
6112421 to
e177f53
Compare
98d3e76 to
ec28821
Compare
e177f53 to
e2cad29
Compare
ec28821 to
b7a4203
Compare
e2cad29 to
6a66aa9
Compare
b7a4203 to
7235dcd
Compare
|
Thanks @james-russo-rames-d-jusso and @miguel-heygen. Fixed:
Design / acknowledged:
|
7235dcd to
ed7dc4f
Compare
af1d91b to
5d42eba
Compare
…on-change Stage 7 Step 3a — SDK plumbing for routing Studio commits through the SDK session. No behavior change: the session stays idle (no op routed yet). SDK: - Add OpenCompositionOptions.persistPath; thread to createPersistQueue so the persist queue writes back to the composition's real path instead of the "composition.html" default (blocker A). Studio (useSdkSession): - Pass persistPath = activeCompPath so a future dispatch persists the right file. - Re-open the session when the active composition file changes on disk (HMR hf:file-change / SSE file-change), scoped to activeCompPath, so the in-memory linkedom document never goes stale under code-editor/agent/server edits (blocker C). Re-opening is additive while the session is idle; 3c must add self-write suppression once dispatch writes. Tests: SDK persistPath default + override; shouldReloadSdkSession path-match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
ed7dc4f to
d86e97c
Compare
5d42eba to
7c17dd9
Compare

What
Brief description of the change.
Why
Why is this change needed?
How
How was this implemented? Any notable design decisions?
Test plan
How was this tested?