Skip to content

fix #604: load config before the first MoQ derive on cold load#614

Merged
streamer45 merged 7 commits into
mainfrom
devin/1782066386-fix-604-moq-server-url
Jun 25, 2026
Merged

fix #604: load config before the first MoQ derive on cold load#614
streamer45 merged 7 commits into
mainfrom
devin/1782066386-fix-604-moq-server-url

Conversation

@staging-devin-ai-integration

@staging-devin-ai-integration staging-devin-ai-integration Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Review & Validation

  • Confirm config and samples still load in parallel (the await configReady only gates the derive, not the sample fetch), so the sample list isn't slowed by config latency.
  • Confirm a config-load failure still lets the view render: loadConfig() never rejects (it sets configLoaded + an error message), so configReady always resolves and the derive proceeds (URL simply stays empty for manual entry).
  • just lint and just test-ui pass. New StreamView.loadSamples.test.ts reproduces the cold-load ordering (sample resolves first) and asserts the derive is deferred until config is ready.

Notes

Supersedes the previous re-resolve-effect approach in this PR after review feedback: rather than patching the late configServerUrl reactively in the hook, the load order is fixed in StreamView so the first derive is always correct. loadAndApplySamples is now exported for unit testing the ordering guarantee.

Link to Devin session: https://staging.itsdev.in/sessions/cbcff0155010454caacdde19106f1b20
Requested by: @streamer45


Devin Review

Status Commit
🟢 Reviewed 76d1979
Open in Devin Review (Staging)

On a cold load of the Stream view, listDynamicSamples() can resolve before loadConfig() populates configServerUrl. The first derive of a gateway_path-only pipeline then resolves with an empty configServerUrl, so resolveServerUrl() returns undefined, setServerUrl is skipped, and the server URL field stays blank until the user manually edits the client section.

useMoqYamlSync now re-resolves the server URL from the last derived settings when configServerUrl transitions from empty to set, so the field populates without manual interaction.

Refs #604

Signed-off-by: streamkit-devin <devin@streamkit.dev>
@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@codecov

codecov Bot commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.97%. Comparing base (c49a064) to head (76d1979).

Additional details and impacted files
@@           Coverage Diff            @@
##             main     #614    +/-   ##
========================================
  Coverage   84.97%   84.97%            
========================================
  Files         247      248     +1     
  Lines       74605    74624    +19     
  Branches     2378     2508   +130     
========================================
+ Hits        63393    63411    +18     
- Misses      11207    11208     +1     
  Partials        5        5            
Flag Coverage Δ
backend 84.97% <ø> (ø)
ui 84.95% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
core 85.64% <ø> (ø)
engine 83.68% <ø> (ø)
api 91.14% <ø> (ø)
nodes 84.54% <ø> (ø)
server 84.78% <ø> (ø)
plugin-native 84.79% <ø> (ø)
plugin-wasm 95.41% <ø> (ø)
ui-services 86.27% <ø> (-0.03%) ⬇️
ui-components 71.96% <ø> (ø)
Files with missing lines Coverage Δ
ui/src/views/streamSamples.ts 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

staging-devin-ai-integration[bot]

This comment was marked as resolved.

…shot

Address review on #604 fix:
- When configServerUrl loads while a YAML edit is mid-debounce, re-resolve
  from the in-flight YAML instead of the stale lastSettingsRef, avoiding a
  transient wrong server URL.
- Make the re-resolve fire exactly once on first config load so a later
  reset of configServerUrl can't clobber a manually edited server URL.

Refs #604

Signed-off-by: streamkit-devin <devin@streamkit.dev>
staging-devin-ai-integration[bot]

This comment was marked as resolved.

staging-devin-ai-integration[bot]

This comment was marked as resolved.

Resolve the cold-load blank server URL (#604) at its source: the Stream
view now starts the config load alongside the sample load and awaits it
before the first derive, so resolveServerUrl always sees a populated
configServerUrl. This removes the reactive re-resolve effect and its
coordinating refs (lastSettingsRef/prevConfigServerUrlRef/
configLoadHandledRef) from useMoqYamlSync.

Signed-off-by: streamkit-devin <devin@streamkit.dev>
@staging-devin-ai-integration staging-devin-ai-integration Bot changed the title fix #604: re-resolve MoQ server URL when config loads after samples fix #604: load config before the first MoQ derive on cold load Jun 24, 2026

@staging-devin-ai-integration staging-devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

Open in Devin Review (Staging)
Debug

Playground

Comment thread ui/src/views/StreamView.loadSamples.test.ts Outdated
Comment on lines 654 to 658
useEffect(() => {
loadAndApplySamples(viewState, deriveMoqFromYaml);
const configReady = configLoaded ? Promise.resolve() : loadConfig();
void loadAndApplySamples(viewState, deriveMoqFromYaml, configReady);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

@staging-devin-ai-integration staging-devin-ai-integration Bot Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

📝 Info: Behavioral change: loadConfig no longer retries on re-render

The old code called loadConfig in a separate useEffect with [configLoaded, loadConfig] dependencies, meaning it would re-fire on any re-render where configLoaded was still false. The new code at StreamView.tsx:655 calls loadConfig() only once on mount (inside a []-deps effect). In practice this is equivalent because loadConfig always sets configLoaded: true (even on error, per ui/src/stores/streamStore.ts:259), so the old effect would never re-fire either. The new approach is actually cleaner since it avoids the theoretical risk of concurrent loadConfig calls if a re-render happened before the first call completed.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

Move the cold-load config sequencing into loadAndApplySamples so all of the
ordering logic is exercised by the unit test, lifting patch coverage. The
component effect reverts to a single call into the function.

Refs #604

Signed-off-by: streamkit-devin <devin@streamkit.dev>

@staging-devin-ai-integration staging-devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

Open in Devin Review (Staging)
Debug

Playground

Comment thread ui/src/views/StreamView.tsx Outdated
Comment thread ui/src/views/StreamView.tsx Outdated
Comment on lines +96 to +97
const { configLoaded, loadConfig } = useStreamStore.getState();
const configReady = configLoaded ? Promise.resolve() : loadConfig();

@staging-devin-ai-integration staging-devin-ai-integration Bot Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

📝 Info: No deduplication guard on concurrent loadConfig calls across views

The loadConfig function in ui/src/stores/streamStore.ts:240-264 has no deduplication logic (no in-flight promise caching). If a user navigates quickly between StreamView and MonitorView, both loadAndApplySamples (ui/src/views/streamSamples.ts:32) and useMonitorPreview (ui/src/hooks/useMonitorPreview.ts:246) could call loadConfig() concurrently, resulting in duplicate fetchConfig() network requests. This is pre-existing and harmless (the second call overwrites with the same values), but could be improved with a simple promise-caching pattern if config fetches become expensive.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

…Samples

Apply selection and derive synchronously after config is awaited so a
user template switch during config load is not clobbered by the
auto-select, and align the test mock's selectedTemplateId with the
production empty-string initial value. Adds a clobber regression test.

Refs #604

Signed-off-by: streamkit-devin <devin@streamkit.dev>

@staging-devin-ai-integration staging-devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

Open in Devin Review (Staging)
Debug

Playground

Comment thread ui/src/views/StreamView.tsx Outdated
Comment thread ui/src/views/StreamView.loadSamples.test.ts Outdated
Comment thread ui/src/views/StreamView.tsx Outdated
Comment on lines +99 to +104
const { configLoaded, loadConfig } = useStreamStore.getState();
try {
viewState.setSamplesLoading(true);
viewState.setSamplesError(null);
const samples = await listDynamicSamples();
const orderedSamples = orderSamplePipelinesSystemFirst(samples);
const samplesPromise = listDynamicSamples();
if (!configLoaded) await loadConfig();

@staging-devin-ai-integration staging-devin-ai-integration Bot Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

📝 Info: Config loading is now exclusively triggered by sample loading in StreamView

The old code had a standalone useEffect that called loadConfig() independently of sample loading. The new code only triggers loadConfig() inside loadAndApplySamples (ui/src/views/streamSamples.ts:32). This means config loading in the StreamView context is now tightly coupled to sample loading — if loadAndApplySamples were ever skipped or short-circuited before the Promise.all, config would never load for this view. Currently this isn't an issue since the function always reaches the Promise.all, but it's a coupling worth being aware of. The useMonitorPreview hook (ui/src/hooks/useMonitorPreview.ts:245-246) independently calls loadConfig, so the Monitor view is unaffected.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

Move the cold-load sample/config sequencing out of StreamView into a
standalone streamSamples module so its unit test imports only the helper
instead of the full view (restores codecov/project/ui, which dropped when
the test pulled the heavy StreamView tree into v8 coverage).

Fetch config and samples with Promise.all so a sample-fetch rejection
during the config await no longer surfaces as an unhandled rejection,
while keeping config loaded before the first MoQ derive (#604).

Drop the clobber test: it mutated a plain object, which does not reflect
React state updates, and the scenario it claimed to guard cannot occur
(the template dropdown is empty until after the config await). Add an
error-path test covering setSamplesError/setSamplesLoading instead.

Signed-off-by: streamkit-devin <devin@streamkit.dev>

@staging-devin-ai-integration staging-devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review (Staging)
Debug

Playground

Comment on lines +31 to +42
const [, samples] = await Promise.all([
configLoaded ? Promise.resolve() : loadConfig(),
listDynamicSamples(),
]);
const orderedSamples = orderSamplePipelinesSystemFirst(samples);
viewState.setSamples(orderedSamples);

if (orderedSamples.length > 0 && !viewState.selectedTemplateId) {
const first = orderedSamples[0];
viewState.setSelectedTemplateId(first.id);
viewState.setPipelineYaml(first.yaml);
deriveMoqFromYaml(first.yaml);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

📝 Info: Config failure is silently absorbed by Promise.all — by design

Since loadConfig() (ui/src/stores/streamStore.ts:240-264) has an internal try/catch and never rejects, a config fetch failure won't cause the Promise.all at ui/src/views/streamSamples.ts:31-34 to reject. Instead, configLoaded is set to true with an empty configServerUrl and an errorMessage in the store. The deriveMoqFromYaml call at line 42 will then resolve the server URL against an empty configServerUrl, resulting in no URL being set (resolveServerUrl at ui/src/utils/moqPeerSettings.ts:158 returns undefined when configServerUrl is empty). The user sees the store's error message and can manually enter a URL. This is correct behavior but worth understanding — a config failure doesn't block sample display.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

@streamer45 streamer45 enabled auto-merge (squash) June 25, 2026 08:14
@streamer45 streamer45 merged commit cadb70e into main Jun 25, 2026
26 checks passed
@streamer45 streamer45 deleted the devin/1782066386-fix-604-moq-server-url branch June 25, 2026 08:24
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.

Stream view: cold-load race between sample load and config load can leave MoQ server URL blank

2 participants