Skip to content

Commit ba345f5

Browse files
os-zhuangclaude
andcommitted
fix(adr-0047): verification-round fixes — switcher schema fallback, hollow-view hydration, getView type bug
- ListView reads showViewSwitcher from the schema too (ObjectView/ InterfaceListPage stamp it there; the prop-only read left the switcher permanently hidden) - InterfaceListPage: prefer source-view candidates that actually carry columns, and hydrate hollow ADR-0017 expansion items through listViewOverrides()/getView() — the same path ObjectView uses - data-objectstack getView(): pass 'view' as the metadata type instead of the object name (hit /meta/<object>/<view> and always 404ed — latent since ADR-0017) All verified live in the browser: data mode (tabs + dropdowns + Grid/ Kanban/Gallery/Calendar switcher, tab filter 10→2 records) and interface mode (inherited columns, page-curated dropdowns only, locked grid, closed toolbar, dropdown filter 10→2 records). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 19b9d21 commit ba345f5

3 files changed

Lines changed: 56 additions & 12 deletions

File tree

packages/app-shell/src/views/InterfaceListPage.tsx

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@ interface InterfaceListPageProps {
3535
*/
3636
function resolveSourceView(objectDef: any, sourceView?: string): any | undefined {
3737
const views: Record<string, any> = objectDef?.listViews || objectDef?.list_views || {};
38-
if (sourceView) {
39-
return (
40-
views[`${objectDef.name}.${sourceView}`]
41-
?? views[sourceView]
42-
?? (sourceView === 'default' || sourceView === 'list' ? objectDef?.list : undefined)
43-
);
44-
}
45-
return objectDef?.list ?? Object.values(views)[0];
38+
// ADR-0017 expansion can serve a default-view item with an empty config
39+
// while the full body lives on `objectDef.list` — prefer candidates that
40+
// actually carry columns over hollow name matches.
41+
const candidates = sourceView
42+
? [
43+
views[`${objectDef?.name}.${sourceView}`],
44+
views[sourceView],
45+
...(sourceView === 'default' || sourceView === 'list' ? [objectDef?.list] : []),
46+
]
47+
: [objectDef?.list, ...Object.values(views)];
48+
const present = candidates.filter(Boolean);
49+
return present.find((v: any) => v?.columns) ?? present[0];
4650
}
4751

4852
export function InterfaceListPage({ page, className }: InterfaceListPageProps) {
@@ -55,11 +59,45 @@ export function InterfaceListPage({ page, className }: InterfaceListPageProps) {
5559
() => (objects || []).find((o: any) => o.name === cfg.source),
5660
[objects, cfg.source],
5761
);
58-
const viewDef = React.useMemo(
62+
const resolvedView = React.useMemo(
5963
() => resolveSourceView(objectDef, cfg.sourceView),
6064
[objectDef, cfg.sourceView],
6165
);
6266

67+
// The view list endpoint can serve hollow expansion items (no columns);
68+
// the full body lives behind the per-view overlay API — the same
69+
// hydration ObjectView performs. Only fetch when the resolution came up
70+
// hollow.
71+
const [hydratedView, setHydratedView] = React.useState<any>(null);
72+
React.useEffect(() => {
73+
let cancelled = false;
74+
setHydratedView(null);
75+
if (!objectDef || !cfg.source || resolvedView?.columns) return;
76+
const viewKey = resolvedView?.name
77+
?? (cfg.sourceView ? `${cfg.source}.${cfg.sourceView}` : undefined);
78+
if (!viewKey) return;
79+
(async () => {
80+
try {
81+
const ds: any = dataSource;
82+
let full: any = null;
83+
if (typeof ds?.listViewOverrides === 'function') {
84+
const all = await ds.listViewOverrides(cfg.source);
85+
full = all?.[viewKey] ?? null;
86+
}
87+
if (!full?.columns && typeof ds?.getView === 'function') {
88+
full = await ds.getView(cfg.source, viewKey);
89+
}
90+
if (!cancelled && full && typeof full === 'object') setHydratedView(full);
91+
} catch { /* hollow view stays hollow — renderer falls back to defaults */ }
92+
})();
93+
return () => { cancelled = true; };
94+
}, [objectDef, cfg.source, cfg.sourceView, resolvedView, dataSource]);
95+
96+
const viewDef = React.useMemo(
97+
() => (hydratedView ? { ...resolvedView, ...hydratedView } : resolvedView),
98+
[resolvedView, hydratedView],
99+
);
100+
63101
const schema = React.useMemo(() => {
64102
if (!objectDef) return undefined;
65103
const view = viewDef || {};

packages/data-objectstack/src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,8 +1379,10 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
13791379
try {
13801380
const cacheKey = `view:${objectName}:${viewId}`;
13811381
return await this.metadataCache.get(cacheKey, async () => {
1382-
// Try meta.getItem for view metadata
1383-
const result: any = await this.client.meta.getItem(objectName, viewId);
1382+
// Views are an independent metadata type (ADR-0017) — the first
1383+
// getItem argument is the metadata TYPE, not the object name.
1384+
// (Passing objectName here hit /meta/<object>/<view> and always 404ed.)
1385+
const result: any = await this.client.meta.getItem('view', viewId);
13841386
if (result && result.item) return result.item;
13851387
return result ?? null;
13861388
});

packages/plugin-list/src/ListView.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,13 @@ export const ListView = React.forwardRef<ListViewHandle, ListViewProps>(({
318318
onHiddenFieldsChange,
319319
onColumnStateChange,
320320
onRowClick,
321-
showViewSwitcher = false,
321+
showViewSwitcher: showViewSwitcherProp,
322322
...props
323323
}, ref) => {
324+
// The switcher can be enabled either by the host component (prop) or by
325+
// the schema itself (ADR-0047 — ObjectView/InterfaceListPage stamp it on
326+
// the schema when appearance.allowedVisualizations whitelists >1 type).
327+
const showViewSwitcher = showViewSwitcherProp ?? (propSchema as any)?.showViewSwitcher ?? false;
324328
// i18n support for record count and other labels
325329
const { t } = useListViewTranslation();
326330
const { fieldLabel: resolveFieldLabel, actionLabel: resolveActionLabel } = useListFieldLabel();

0 commit comments

Comments
 (0)