Skip to content

Commit 806ee9b

Browse files
os-zhuangclaude
andauthored
fix(app-shell): extractItems accepts bare arrays — preview-mode list reads were silently empty (#1661)
MetadataClient.list() (the ?preview=draft path, ADR-0037) unwraps {items} internally and resolves a BARE ARRAY; extractItems only handled the adapter SDK's {items} envelope and returned [] for arrays. Every preview-mode list read came back empty, so a draft-built app rendered the launcher's 'No Apps Configured' empty state in the Live Canvas (live-verified on staging after the REST passthrough fix landed — the API returned 13 apps, the UI saw zero). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 82b7c97 commit 806ee9b

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { describe, it, expect } from 'vitest';
4+
import { extractItems } from './MetadataProvider';
5+
6+
/**
7+
* The provider consumes TWO metadata sources with different envelope shapes:
8+
* the adapter SDK resolves `{ items: [...] }`, while MetadataClient.list()
9+
* (the `?preview=draft` path, ADR-0037) unwraps internally and resolves a
10+
* BARE ARRAY. Both must normalize identically — the missing array branch
11+
* silently emptied every preview-mode list read, so a draft-built app
12+
* rendered "No Apps Configured" in the Live Canvas.
13+
*/
14+
describe('extractItems', () => {
15+
const apps = [{ name: 'procurement_system' }, { name: 'crm' }];
16+
17+
it('unwraps the adapter SDK `{items}` envelope', () => {
18+
expect(extractItems({ items: apps })).toEqual(apps);
19+
});
20+
21+
it('passes through a bare array (MetadataClient.list / preview-draft path)', () => {
22+
expect(extractItems(apps)).toEqual(apps);
23+
});
24+
25+
it('returns [] for malformed responses', () => {
26+
expect(extractItems(null)).toEqual([]);
27+
expect(extractItems({ data: apps })).toEqual([]);
28+
expect(extractItems('nope')).toEqual([]);
29+
});
30+
});

packages/app-shell/src/providers/MetadataProvider.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,14 @@ function debug(...args: unknown[]) {
8282
// Helpers
8383
// ---------------------------------------------------------------------------
8484

85-
function extractItems(res: unknown): any[] {
85+
/** Exported for tests — both metadata sources must normalize identically. */
86+
export function extractItems(res: unknown): any[] {
87+
// MetadataClient.list() (the preview-draft path) unwraps `{items}` itself
88+
// and resolves a BARE ARRAY; the adapter SDK path resolves `{items}`.
89+
// Missing this branch silently turned every preview-mode list read into []
90+
// — a draft-built app rendered the launcher's "No Apps Configured" empty
91+
// state in the Live Canvas (live-verified on staging).
92+
if (Array.isArray(res)) return res;
8693
if (res && typeof res === 'object' && 'items' in res && Array.isArray((res as { items: unknown[] }).items)) {
8794
return (res as { items: unknown[] }).items;
8895
}

0 commit comments

Comments
 (0)