Fix console stuck on loading screen due to MSW metadata route mismatch#518
Fix console stuck on loading screen due to MSW metadata route mismatch#518
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
… URLs The @objectstack/client sends metadata requests using singular type names (e.g. GET /api/v1/meta/app, /meta/object) but the MSW handlers only registered plural routes (/meta/apps, /meta/objects), causing all metadata fetches to fail with 500 errors on the demo site. Replace hardcoded plural-only handlers with dynamic /meta/:type and /metadata/:type/:name routes that match both singular and plural forms. Also fix RootRedirect to show an error message instead of an infinite loading screen when no apps are available. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a critical bug where the console demo (demo.objectui.org) was stuck on an infinite loading screen. The root cause was a mismatch between the metadata routes expected by @objectstack/client v3.0.4 (which sends singular type names like /meta/app, /meta/object) and the MSW mock handlers (which only matched plural forms like /meta/apps, /meta/objects).
Changes:
- Replaced 12 hardcoded plural-only MSW metadata handlers with 2 dynamic
:typeparameter handlers that accept both singular and plural metadata type names - Updated RootRedirect component to display an actionable Empty state (instead of an infinite LoadingScreen) when metadata loads successfully but contains no apps or when an error occurs
- Added
EmptyDescriptionimport to display helpful error messages in the empty state
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| apps/console/src/mocks/handlers.ts | Consolidated 12 hardcoded metadata handlers into 4 dynamic handlers using :type parameter to match both singular and plural metadata routes; removed console.error logging from error handlers |
| apps/console/src/App.tsx | Added error handling to RootRedirect with Empty state UI instead of infinite LoadingScreen; imported EmptyDescription component for better error messaging |
| } catch (e) { | ||
| console.error('[MSW] error getting meta item', e); | ||
| return HttpResponse.json({ error: String(e) }, { status: 500 }); | ||
| } |
There was a problem hiding this comment.
The console.error logging was removed from the error handling block. This will make debugging MSW handler failures more difficult. Consider keeping the error logging while still returning the error response, for example:
} catch (e) {
console.error('[MSW] error getting meta item', e);
return HttpResponse.json({ error: String(e) }, { status: 500 });
}This applies to both /meta/:type/:name and /metadata/:type/:name handlers.
| <Empty> | ||
| <EmptyTitle>{error ? 'Failed to Load Configuration' : 'No Apps Configured'}</EmptyTitle> | ||
| <EmptyDescription> | ||
| {error ? error.message : 'No applications have been registered. Check your ObjectStack configuration.'} | ||
| </EmptyDescription> | ||
| </Empty> | ||
| </div> |
There was a problem hiding this comment.
The Empty state implementation is missing a visual icon/media element that is consistently used across other Empty states in the codebase. For consistency and better UX, consider adding an icon similar to other error/empty states:
<Empty>
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted">
<AlertCircle className="h-6 w-6 text-muted-foreground" />
</div>
<EmptyTitle>{error ? 'Failed to Load Configuration' : 'No Apps Configured'}</EmptyTitle>
<EmptyDescription>
{error ? error.message : 'No applications have been registered. Check your ObjectStack configuration.'}
</EmptyDescription>
</Empty>You'll need to import AlertCircle or a similar icon from lucide-react. See DashboardView.tsx:39-48, ErrorBoundary.tsx:30-38, or PageView.tsx:25-34 for examples of this pattern.
| http.get(`${prefix}${baseUrl}/meta/:type`, async ({ params }) => { | ||
| const metadataType = params.type as string; | ||
| const response = await protocol.getMetaItems({ type: metadataType }); | ||
| return HttpResponse.json(response, { status: 200 }); | ||
| }), | ||
| http.get(`${prefix}${baseUrl}/metadata/objects`, async () => { | ||
| const response = await protocol.getMetaItems({ type: 'object' }); | ||
| http.get(`${prefix}${baseUrl}/metadata/:type`, async ({ params }) => { | ||
| const metadataType = params.type as string; | ||
| const response = await protocol.getMetaItems({ type: metadataType }); | ||
| return HttpResponse.json(response, { status: 200 }); | ||
| }), | ||
|
|
||
| // ── Metadata: single object (legacy /meta/objects/:name) ───────────── | ||
| http.get(`${prefix}${baseUrl}/meta/objects/:objectName`, async ({ params }) => { | ||
| // ── Metadata: single item by type + name ───────────────────────────── | ||
| http.get(`${prefix}${baseUrl}/meta/:type/:name`, async ({ params }) => { | ||
| try { | ||
| const response = await protocol.getMetaItem({ | ||
| type: 'object', | ||
| name: params.objectName as string | ||
| }); | ||
| return HttpResponse.json(response || { error: 'Not found' }, { status: response ? 200 : 404 }); | ||
| } catch (e) { | ||
| return HttpResponse.json({ error: String(e) }, { status: 500 }); | ||
| } | ||
| }), | ||
|
|
||
| // ── Metadata: single object (/meta/object/:name & /metadata/object/:name) | ||
| http.get(`${prefix}${baseUrl}/meta/object/:objectName`, async ({ params }) => { | ||
| try { | ||
| const response = await protocol.getMetaItem({ | ||
| type: 'object', | ||
| name: params.objectName as string | ||
| type: params.type as string, | ||
| name: params.name as string | ||
| }); | ||
| const payload = (response && response.item) ? response.item : response; | ||
| return HttpResponse.json(payload || { error: 'Not found' }, { status: payload ? 200 : 404 }); | ||
| } catch (e) { | ||
| console.error('[MSW] error getting meta item', e); | ||
| return HttpResponse.json({ error: String(e) }, { status: 500 }); | ||
| } | ||
| }), | ||
|
|
||
| http.get(`${prefix}${baseUrl}/metadata/object/:objectName`, async ({ params }) => { | ||
| http.get(`${prefix}${baseUrl}/metadata/:type/:name`, async ({ params }) => { | ||
| try { | ||
| const response = await protocol.getMetaItem({ | ||
| type: 'object', | ||
| name: params.objectName as string | ||
| type: params.type as string, | ||
| name: params.name as string | ||
| }); | ||
| const payload = (response && response.item) ? response.item : response; | ||
| return HttpResponse.json(payload || { error: 'Not found' }, { status: payload ? 200 : 404 }); | ||
| } catch (e) { | ||
| console.error('[MSW] error getting meta item', e); | ||
| return HttpResponse.json({ error: String(e) }, { status: 500 }); | ||
| } | ||
| }), |
There was a problem hiding this comment.
For clarity and to follow MSW best practices, consider reordering the metadata handlers so that more specific routes (with more path segments) are defined before less specific routes. While MSW's path-to-regexp should handle this correctly based on specificity, explicit ordering makes the code more maintainable and less prone to subtle bugs:
/meta/:type/:name(lines 68-79)/metadata/:type/:name(lines 80-91)/meta/:type(lines 56-60)/metadata/:type(lines 61-65)
This ensures that requests like /meta/object/User will definitely match the :type/:name handler rather than relying on MSW's internal matching algorithm.
demo.objectui.org permanently stuck on initialization screen.
@objectstack/clientv3.0.4 sends singular metadata routes (GET /meta/app,/meta/object) but MSW handlers only registered plural routes (/meta/apps,/meta/objects), causing all 5 metadata fetches to 500.MSW handlers (
handlers.ts)Replaced 12 hardcoded plural-only handlers with dynamic
:typeroutes:RootRedirect (
App.tsx)When metadata loads but yields no apps,
RootRedirectrendered<LoadingScreen />indefinitely. Now shows an actionable empty state with error context.Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.