diff --git a/frontend/src/app/pages/AgentChat/tool-ui/ShowUiWidgetView.tsx b/frontend/src/app/pages/AgentChat/tool-ui/ShowUiWidgetView.tsx
index 849446d8..fada029f 100644
--- a/frontend/src/app/pages/AgentChat/tool-ui/ShowUiWidgetView.tsx
+++ b/frontend/src/app/pages/AgentChat/tool-ui/ShowUiWidgetView.tsx
@@ -34,8 +34,8 @@ function ShowUiWidgetView({ payload, ambient }: { payload: ShowUiPayload; ambien
const fallback = [raw.src, raw.url].find((v): v is string => typeof v === 'string');
if (fallback) nav.href = fallback;
}
- const shaped = ambient && !perfBaselineFor('ambient') ? ambientShape(payload.name, raw) : { props: payload.props, note: null };
- let widget = ;
+ const shaped = ambient && !perfBaselineFor('ambient') ? ambientShape(payload.name, raw) : { props: payload.props, note: null, extraProps: {} };
+ let widget = ;
if (shaped.note) {
widget = (
diff --git a/frontend/src/app/pages/AgentChat/tool-ui/showUiAmbient.test.ts b/frontend/src/app/pages/AgentChat/tool-ui/showUiAmbient.test.ts
index 9c0ae779..2c7ab57b 100644
--- a/frontend/src/app/pages/AgentChat/tool-ui/showUiAmbient.test.ts
+++ b/frontend/src/app/pages/AgentChat/tool-ui/showUiAmbient.test.ts
@@ -24,11 +24,14 @@ test('a short table and other widgets pass through untouched', () => {
test('a pill stats card is asked for its compact density', () => {
const shaped = ambientShape('stats-display', { stats: [] });
- assert.equal(shaped.props.compact, true);
+ // The wire schema names no compact key and the strict parse strips unknown keys, so the flag rides extraProps (merged after the gate); in the wire props it was inert.
+ assert.equal(shaped.extraProps.compact, true);
+ assert.equal(shaped.props.compact, undefined);
});
test('the widget view applies the shaping only on the ambient surface, and is memoized', () => {
const src = fs.readFileSync(path.join(process.cwd(), 'src/app/pages/AgentChat/tool-ui/ShowUiWidgetView.tsx'), 'utf8');
assert.ok(src.includes("ambient && !perfBaselineFor('ambient') ? ambientShape(payload.name, raw)"), 'the chat keeps the whole table; only the pill is shaped');
+ assert.ok(src.includes('extraProps={{ ...nav, ...shaped.extraProps }}'), 'the shaped extras are merged after the zod gate');
assert.ok(src.includes("export default perfBaselineFor('ambient') ? ShowUiWidgetView : React.memo(ShowUiWidgetView)"));
});
diff --git a/frontend/src/app/pages/AgentChat/tool-ui/showUiAmbient.ts b/frontend/src/app/pages/AgentChat/tool-ui/showUiAmbient.ts
index 0ff1bf8b..24ff335d 100644
--- a/frontend/src/app/pages/AgentChat/tool-ui/showUiAmbient.ts
+++ b/frontend/src/app/pages/AgentChat/tool-ui/showUiAmbient.ts
@@ -9,6 +9,8 @@ export interface AmbientShape {
props: Record;
/** One line under the widget when something was left out, or null. */
note: string | null;
+ /** Merged AFTER the zod gate: the strict parse strips every key the wire schema does not name, which is where a compact flag in the wire props silently died. */
+ extraProps: Record;
}
export function ambientShape(name: string, props: Record): AmbientShape {
@@ -17,9 +19,10 @@ export function ambientShape(name: string, props: Record): Ambi
return {
props: { ...props, data: props.data.slice(0, AMBIENT_TABLE_ROWS) },
note: `Showing ${AMBIENT_TABLE_ROWS} of ${total.toLocaleString()} rows. Open the chat for the whole table.`,
+ extraProps: {},
};
}
// The vendored stats card stacks its cells vertically under 440 px; the pill is narrower than that.
- if (name === 'stats-display') return { props: { ...props, compact: true }, note: null };
- return { props, note: null };
+ if (name === 'stats-display') return { props, note: null, extraProps: { compact: true } };
+ return { props, note: null, extraProps: {} };
}