Skip to content

fix(UI): prevent startup spinner stall when external model resolution hangs#860

Open
divyansh-cyber wants to merge 1 commit intoaccordproject:mainfrom
divyansh-cyber:fixing_859
Open

fix(UI): prevent startup spinner stall when external model resolution hangs#860
divyansh-cyber wants to merge 1 commit intoaccordproject:mainfrom
divyansh-cyber:fixing_859

Conversation

@divyansh-cyber
Copy link
Copy Markdown

Fixes: #859
This PR fixes a startup reliability issue where the app could remain on the loading spinner for a long time (or indefinitely) when external model resolution stalls.

Problem:
Initialization awaited rebuild, and rebuild always awaited external model resolution with no timeout/fallback path. In degraded/offline environments this could block app startup and delay access to the editor UI.

What changed:

  • Added timeout protection around external model resolution in store.ts.
  • Only attempted external model resolution when the model contains imports (remote resolution actually needed).
  • Added initialization timeout guard in App.tsx so loading state always exits instead of spinning indefinitely.

Expected behavior after this PR:

  • App no longer stays indefinitely on spinner during slow/unavailable external model fetch scenarios.
  • Startup fails fast with an error path and app shell can continue rendering behavior is no longer blocked forever.

Scope:

  • Focused only on startup/rebuild flow for the loading spinner stall issue.
  • No functional dependency changes required for this fix.

Author Checklist

  • Ensure you provide a DCO sign-off for your commits using the --signoff option of git commit.
  • Vital features and changes captured in unit and/or integration tests
  • Commits messages follow AP format

…ion stall

Adds timeout guards to startup initialization and external model resolution,
and limits external resolution to models that actually declare imports.
This prevents the app from remaining on loading spinner indefinitely
under degraded network conditions.

Signed-off-by: Divyansh Rai <140232173+divyansh-cyber@users.noreply.github.com>
@divyansh-cyber divyansh-cyber requested a review from a team as a code owner March 26, 2026 19:15
Copilot AI review requested due to automatic review settings March 26, 2026 19:15
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 26, 2026

Deploy Preview for ap-template-playground ready!

Name Link
🔨 Latest commit 555c8cd
🔍 Latest deploy log https://app.netlify.com/projects/ap-template-playground/deploys/69c585e22d17710008326be9
😎 Deploy Preview https://deploy-preview-860--ap-template-playground.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a startup reliability issue in the Template Playground where the app could remain stuck on the initial loading spinner if external Concerto model resolution stalls, ensuring the editor UI becomes reachable even in degraded/offline conditions.

Changes:

  • Added a timeout wrapper around ModelManager.updateExternalModels() and skipped external resolution when the CTO model has no import statements.
  • Added an app initialization timeout guard so the startup spinner cannot block indefinitely.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/store/store.ts Adds timeout + conditional external model resolution during rebuild to prevent rebuild from hanging on remote imports.
src/App.tsx Adds initialization timeout guard around init() / loadFromLink() to ensure loading spinner always exits.
Comments suppressed due to low confidence (1)

src/App.tsx:79

  • When initialization times out, the error is only logged to the console and loading is set to false. That leaves users with a blank/partially initialized editor (e.g., empty preview) without any visible indication of what happened. Consider surfacing the timeout failure in the UI (e.g., set store error + open the problem panel, or show an AntD message.error) so degraded/offline startup has a clear user-facing failure path.
      } catch (error) {
        console.error("Initialization error:", error);
      } finally {
        setLoading(false);
      }

Comment thread src/App.tsx
Comment on lines +60 to 74
await withTimeout(
loadFromLink(compressedData),
APP_INIT_TIMEOUT_MS,
`Initialization timed out after ${APP_INIT_TIMEOUT_MS}ms while loading shared data.`
);
if (window.location.pathname !== "/") {
navigate("/", { replace: true });
}
} else {
await init();
await withTimeout(
init(),
APP_INIT_TIMEOUT_MS,
`Initialization timed out after ${APP_INIT_TIMEOUT_MS}ms.`
);
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The new initialization timeout behavior is user-facing (spinner should stop, and a failure path should be shown). There are existing component/store unit tests in the repo, but no coverage here to ensure the spinner reliably exits and the app renders the shell when init()/loadFromLink() hangs. Please add a unit test (Vitest + fake timers) that forces a never-resolving init promise and asserts loading state transitions / error surfacing.

Copilot generated this review using guidance from repository custom instructions.
Comment thread src/store/store.ts
Comment on lines +82 to +116
const EXTERNAL_MODEL_RESOLUTION_TIMEOUT_MS = 10000;

function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {
return new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(message)), ms);
promise
.then((value) => {
clearTimeout(timer);
resolve(value);
})
.catch((error: unknown) => {
clearTimeout(timer);
reject(error);
});
});
}

function hasExternalImports(model: string): boolean {
return /^\s*import\s+/m.test(model);
}

async function rebuild(template: string, model: string, dataString: string): Promise<string> {
// Validate inputs before expensive operations
// This fails fast on invalid JSON or CTO syntax without running network calls
await validateBeforeRebuild(template, model, dataString);

const modelManager = new ModelManager({ strict: true });
modelManager.addCTOModel(model, undefined, true);
await modelManager.updateExternalModels();
const engine = new TemplateMarkInterpreter(modelManager, {});
if (hasExternalImports(model)) {
await withTimeout(
modelManager.updateExternalModels(),
EXTERNAL_MODEL_RESOLUTION_TIMEOUT_MS,
`External model resolution timed out after ${EXTERNAL_MODEL_RESOLUTION_TIMEOUT_MS}ms. Check connectivity or remove remote imports.`
);
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The external model resolution timeout and the new hasExternalImports(model) gate are important behavior changes in the rebuild pipeline, but there’s no unit coverage to prevent regressions. Please add store-level tests that (1) verify updateExternalModels() is not called when the model has no imports, and (2) verify a hanging updateExternalModels() causes rebuild to fail within the timeout and sets the store error / shows the problem panel.

Copilot generated this review using guidance from repository custom instructions.
Comment thread src/App.tsx
try {
await Promise.race([promise, timeoutPromise]);
} finally {
if (timeoutHandle) {
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

withTimeout clears the timer only when timeoutHandle is truthy. In browsers (and especially in some test environments), setTimeout can return 0, which would skip the cleanup and leave the timer running. Use an explicit timeoutHandle !== undefined check (or initialize to null) so the timeout is always cleared when set.

Suggested change
if (timeoutHandle) {
if (timeoutHandle !== undefined) {

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown

This PR is stale because it has been open 15 days with no activity. Remove stale label or comment or this will be closed in 10 days.

@github-actions github-actions bot added the Stale label Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App can stay on loading spinner indefinitely when external model resolution stalls

2 participants