Skip to content

Commit ce268a6

Browse files
os-zhuangCopilot
andcommitted
feat(metadata-admin): provenance badges + navigation fix
Surface the two-tier model on the metadata list: - Derive 'artifact' vs 'runtime' from item._packageId (sentinel 'sys_metadata' counted as runtime); honor server-provided 'source' field when present - Render colored badges: sky-blue '代码包/Artifact' (with packageId tooltip), emerald 'runtime/运行时' — replacing the always-'—' cell - Wire the source filter dropdown to actually filter rows; counts reflect the new two-bucket model - Add i18n keys engine.list.source.{artifact,runtime,*Desc} in EN+ZH Fix relative navigation: navigate('../foo') was resolved against the matched route pattern rather than the current pathname, dropping the ':type' segment (e.g. /metadata/hook/new → /metadata/new). Pass { relative: 'path' } on all four call sites in ResourceEditPage so save/cancel/history/reset/create-from-locked-banner all land on the correct URL within the type. Browser-verified on hook: - both badges render with correct colors and Chinese labels - source filter narrows count + filtered count correctly - '新建' on artifact detail page now goes to /metadata/hook/new Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 430981d commit ce268a6

3 files changed

Lines changed: 57 additions & 28 deletions

File tree

packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ export function MetadataResourceEditPage({
341341
// navigation to the new record's URL will reset state anyway).
342342
if (!createMode) setEditing(false);
343343
if (createMode) {
344-
navigate(`../${encodeURIComponent(savedName)}`);
344+
navigate(`../${encodeURIComponent(savedName)}`, { relative: 'path' });
345345
}
346346
} catch (err: any) {
347347
// Map destructive change → confirmation dialog.
@@ -412,7 +412,7 @@ export function MetadataResourceEditPage({
412412
setEditing(false);
413413
} else {
414414
// No artifact baseline → return to the list view.
415-
navigate(`../`);
415+
navigate(`../`, { relative: 'path' });
416416
}
417417
} catch (err: any) {
418418
setError(err?.message ?? String(err));
@@ -544,7 +544,7 @@ export function MetadataResourceEditPage({
544544
<Button
545545
variant="ghost"
546546
size="sm"
547-
onClick={() => navigate(`./history`)}
547+
onClick={() => navigate(`./history`, { relative: 'path' })}
548548
>
549549
<History className="h-4 w-4 mr-1" />
550550
{t('engine.edit.history', locale)}
@@ -651,7 +651,7 @@ export function MetadataResourceEditPage({
651651
size="sm"
652652
variant="outline"
653653
className="shrink-0"
654-
onClick={() => navigate(`../new`)}
654+
onClick={() => navigate(`../new`, { relative: 'path' })}
655655
>
656656
{t('engine.list.create', locale)}
657657
</Button>

packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,31 @@ type ItemRow = {
4848
raw: any;
4949
/** Flattened item content for display. */
5050
item: Record<string, unknown>;
51-
/** Best-effort source classification ('code' | 'overlay' | 'effective' | undefined). */
52-
source?: string;
51+
/**
52+
* Provenance classification derived from `_packageId` tag:
53+
* - 'artifact' = shipped by a real code package
54+
* - 'runtime' = authored at runtime (DB-only, no packageId or sentinel)
55+
*
56+
* Server may also pre-classify via a top-level `source` field
57+
* ('code' / 'overlay' / 'effective'); we honor that when present
58+
* and fall back to packageId-derived inference otherwise.
59+
*/
60+
source: 'artifact' | 'runtime';
5361
};
5462

63+
/**
64+
* Derive provenance from item._packageId. The `loadMetaFromDb` path
65+
* tags objects with the synthetic packageId 'sys_metadata' (see
66+
* framework protocol.ts:3092); treat that sentinel as runtime-authored.
67+
*/
68+
function classifyProvenance(item: Record<string, unknown>, rawSource?: string): 'artifact' | 'runtime' {
69+
if (rawSource === 'overlay' || rawSource === 'runtime') return 'runtime';
70+
if (rawSource === 'code' || rawSource === 'artifact') return 'artifact';
71+
const pkg = item._packageId as string | undefined;
72+
if (!pkg || pkg === 'sys_metadata') return 'runtime';
73+
return 'artifact';
74+
}
75+
5576
export function MetadataResourceListPage({ type: typeProp }: MetadataResourceListPageProps) {
5677
const params = useParams<{ type?: string }>();
5778
const type = typeProp ?? params.type ?? '';
@@ -95,7 +116,7 @@ function DefaultMetadataList({ type }: { type: string }) {
95116
return {
96117
raw,
97118
item,
98-
source: raw?.source,
119+
source: classifyProvenance(item, raw?.source),
99120
};
100121
});
101122
setItems(rows);
@@ -115,15 +136,15 @@ function DefaultMetadataList({ type }: { type: string }) {
115136
const searchableFields = config.searchableFields ?? ['name', 'label', 'description'];
116137
const filtered = items.filter((row) => {
117138
if (!matchesQuery(row.item, query, searchableFields)) return false;
118-
if (sourceFilter !== 'all' && row.source && row.source !== sourceFilter) return false;
139+
if (sourceFilter !== 'all' && row.source !== sourceFilter) return false;
119140
return true;
120141
});
121142

122143
// Compute source counts for the filter dropdown.
123144
const sourceCounts = React.useMemo(() => {
124-
const c: Record<string, number> = { all: items.length, code: 0, overlay: 0, effective: 0 };
145+
const c = { all: items.length, artifact: 0, runtime: 0 };
125146
for (const r of items) {
126-
if (r.source && c[r.source] !== undefined) c[r.source]++;
147+
c[r.source]++;
127148
}
128149
return c;
129150
}, [items]);
@@ -187,9 +208,8 @@ function DefaultMetadataList({ type }: { type: string }) {
187208
</SelectTrigger>
188209
<SelectContent>
189210
<SelectItem value="all">{t('engine.list.allSources', locale)} ({sourceCounts.all})</SelectItem>
190-
<SelectItem value="code">Code ({sourceCounts.code})</SelectItem>
191-
<SelectItem value="overlay">Overlay ({sourceCounts.overlay})</SelectItem>
192-
<SelectItem value="effective">Effective ({sourceCounts.effective})</SelectItem>
211+
<SelectItem value="artifact">{t('engine.list.source.artifact', locale)} ({sourceCounts.artifact})</SelectItem>
212+
<SelectItem value="runtime">{t('engine.list.source.runtime', locale)} ({sourceCounts.runtime})</SelectItem>
193213
</SelectContent>
194214
</Select>
195215
</div>
@@ -260,21 +280,22 @@ function DefaultMetadataList({ type }: { type: string }) {
260280
);
261281
})}
262282
<td className="px-3 py-2 text-right align-top">
263-
{row.source ? (
264-
<Badge
265-
variant="outline"
266-
className={
267-
'text-[10px] ' +
268-
(row.source === 'overlay'
269-
? 'border-emerald-500 text-emerald-700'
270-
: '')
271-
}
272-
>
273-
{row.source}
274-
</Badge>
275-
) : (
276-
<span className="text-muted-foreground text-xs"></span>
277-
)}
283+
<Badge
284+
variant="outline"
285+
className={
286+
'text-[10px] ' +
287+
(row.source === 'artifact'
288+
? 'border-sky-500/50 text-sky-700 dark:text-sky-300'
289+
: 'border-emerald-500/50 text-emerald-700 dark:text-emerald-300')
290+
}
291+
title={
292+
row.source === 'artifact'
293+
? `${t('engine.list.source.artifactDesc', locale)}${row.item._packageId ? ` (${row.item._packageId})` : ''}`
294+
: t('engine.list.source.runtimeDesc', locale)
295+
}
296+
>
297+
{t(`engine.list.source.${row.source}`, locale)}
298+
</Badge>
278299
</td>
279300
</tr>
280301
);

packages/app-shell/src/views/metadata-admin/i18n.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ const ENGINE_STRINGS_EN: Record<string, string> = {
138138
'engine.list.items': 'Items',
139139
'engine.list.filtered': 'Filtered',
140140
'engine.list.allSources': 'All sources',
141+
'engine.list.source.artifact': 'Artifact',
142+
'engine.list.source.runtime': 'Runtime',
143+
'engine.list.source.artifactDesc': 'Shipped by code package',
144+
'engine.list.source.runtimeDesc': 'Authored at runtime',
141145
'engine.list.col.name': 'Name',
142146
'engine.list.col.label': 'Label',
143147
'engine.list.col.source': 'Source',
@@ -258,6 +262,10 @@ const ENGINE_STRINGS_ZH: Record<string, string> = {
258262
'engine.list.items': '条目',
259263
'engine.list.filtered': '已筛选',
260264
'engine.list.allSources': '全部来源',
265+
'engine.list.source.artifact': '代码包',
266+
'engine.list.source.runtime': '运行时',
267+
'engine.list.source.artifactDesc': '由代码包提供',
268+
'engine.list.source.runtimeDesc': '运行时创建',
261269
'engine.list.col.name': '名称',
262270
'engine.list.col.label': '标签',
263271
'engine.list.col.source': '来源',

0 commit comments

Comments
 (0)