Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ const LearnNow = lazy(() => import("./pages/LearnNow"));
const MainContainer = lazy(() => import("./pages/MainContainer"));

const { Content } = Layout;
const APP_INIT_TIMEOUT_MS = 12000;

const withTimeout = async (promise: Promise<void>, ms: number, message: string): Promise<void> => {
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
const timeoutPromise = new Promise<void>((_, reject) => {
timeoutHandle = setTimeout(() => reject(new Error(message)), ms);
});
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.
clearTimeout(timeoutHandle);
}
}
};

const App = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -42,12 +57,20 @@ const App = () => {
compressedData = searchParams.get("data");
}
if (compressedData) {
await loadFromLink(compressedData);
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.`
);
}
Comment on lines +60 to 74
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.
} catch (error) {
console.error("Initialization error:", error);
Expand Down
32 changes: 30 additions & 2 deletions src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,43 @@ export interface DecompressedData {

const rebuildDeBounce = debounce(rebuild, 500);

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.`
);
}
Comment on lines +82 to +116
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.
// template-engine currently resolves concerto-core v3 types; cast to bridge v4 app types at this boundary.
const engine = new TemplateMarkInterpreter(modelManager as unknown as never, {});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const templateMarkTransformer = new TemplateMarkTransformer();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
Expand Down
Loading