Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
8dc92bf
Add resources discussion
jacobsimionato Mar 5, 2026
c5a8eb3
Improve functions design
jacobsimionato Mar 5, 2026
5df86c2
Add comments about unsubscribing
jacobsimionato Mar 5, 2026
07077b2
Add catalog API proposal doc v1
jacobsimionato Mar 5, 2026
6ae4b24
Update catalog proposal
jacobsimionato Mar 6, 2026
0e75794
Improve catalog API proposal
jacobsimionato Mar 6, 2026
2f52cad
Update renderer guide
jacobsimionato Mar 6, 2026
d9f328f
Add improvements to the renderer guide
jacobsimionato Mar 6, 2026
a4b2b61
Delete catalog proposal
jacobsimionato Mar 6, 2026
f8c147c
remove dumb
jacobsimionato Mar 10, 2026
2a74434
Address feedback
jacobsimionato Mar 10, 2026
46a1504
Improve explanation of scope
jacobsimionato Mar 10, 2026
a17edea
Respond to a few more commments
jacobsimionato Mar 10, 2026
7d73d59
Add initial react renderer
jacobsimionato Mar 10, 2026
1daad96
Update react renderer to fix reactivity
jacobsimionato Mar 11, 2026
a1fa3ec
improve react renderer
jacobsimionato Mar 11, 2026
dc958a6
Improvemeents to react renderer
jacobsimionato Mar 11, 2026
1ceb3f1
More react improvements
jacobsimionato Mar 11, 2026
e773e0f
Add deep generic binder etc
jacobsimionato Mar 12, 2026
f07b00d
MOve react to react_prototype
jacobsimionato Mar 12, 2026
1eebf0d
clean up react renderer
jacobsimionato Mar 12, 2026
b95ab1d
Merge branch 'main' into react-1
jacobsimionato Mar 12, 2026
4168208
Add more samples
jacobsimionato Mar 12, 2026
110f87e
refactor(react): automatically infer component props from Zod schema
jacobsimionato Mar 12, 2026
11399ee
feat(react): implement basic catalog and integrate with gallery app
jacobsimionato Mar 12, 2026
6c113ff
fix(react): robust zod schema matching for generic binder
jacobsimionato Mar 12, 2026
88378d9
refactor(core): move generic binder from react prototype to web core
jacobsimionato Mar 12, 2026
587cdd6
set function invoker properly
jacobsimionato Mar 12, 2026
7a4b788
feat(react): improve generic binder ergonomics for two-way binding
jacobsimionato Mar 13, 2026
99e7095
feat(react): strictly typed setter generation in generic binder
jacobsimionato Mar 13, 2026
502520d
fix(react): remove optional chaining from strictly typed generated se…
jacobsimionato Mar 13, 2026
d2d6069
Improements to binder and add copyright headers
jacobsimionato Mar 13, 2026
64ff985
Improve return type of FunctionInvoker
jacobsimionato Mar 13, 2026
2fcf422
Merge branch 'main' into react-1
jacobsimionato Mar 18, 2026
22f81bb
Merge branch 'main' into react-1
jacobsimionato Mar 19, 2026
dc7f6a6
Add test for basic_catalog etc
jacobsimionato Mar 19, 2026
2774778
Move react renderer in to place
jacobsimionato Mar 19, 2026
7c6ea9b
Move sample app
jacobsimionato Mar 19, 2026
0105697
Revert web_core changes and fix React demo build
jacobsimionato Mar 19, 2026
6d7bd40
Fix lint errors
jacobsimionato Mar 19, 2026
de7c6ee
Fix demo app
jacobsimionato Mar 19, 2026
dbcba08
Merge branch 'main' into react-1
jacobsimionato Mar 19, 2026
7725ee9
Fix package locK
jacobsimionato Mar 19, 2026
059c489
Fix text field
jacobsimionato Mar 19, 2026
ae4372b
Fix text field
jacobsimionato Mar 19, 2026
eb4d3f4
Fix text field
jacobsimionato Mar 19, 2026
d213b55
Fix package.json etc
jacobsimionato Mar 19, 2026
cca2e02
revert text field changes
jacobsimionato Mar 19, 2026
a16520d
Fix CI
jacobsimionato Mar 19, 2026
35e3750
Fix text field
jacobsimionato Mar 20, 2026
4b675e1
Fix CI errors re textfield
jacobsimionato Mar 20, 2026
b360c09
Merge branch 'main' into react-1
jacobsimionato Mar 20, 2026
c771ea7
revert doc changes
jacobsimionato Mar 20, 2026
7e19997
Remove a2ui_explorer app
jacobsimionato Mar 20, 2026
77ac6b4
Move catalogs to better named locations
jacobsimionato Mar 20, 2026
4038c92
Revert "Remove a2ui_explorer app"
jacobsimionato Mar 20, 2026
628f271
REmove basic catalog support
jacobsimionato Mar 20, 2026
00c6755
remove shell app
jacobsimionato Mar 20, 2026
3bb5229
Readd the samples/client/react/shell app
jacobsimionato Mar 20, 2026
16fe726
Revert "REmove basic catalog support"
jacobsimionato Mar 20, 2026
8ec71cd
Add tests
jacobsimionato Mar 20, 2026
8275438
Add more tests
jacobsimionato Mar 20, 2026
a65c1b5
Merge branch 'main' into react-basic-catalog
jacobsimionato Mar 23, 2026
77c2165
remove merge markers from react_renderer.yml
jacobsimionato Mar 23, 2026
c8bf8ef
Fix merge markers etc
jacobsimionato Mar 23, 2026
aca7706
Remove pr comments file
jacobsimionato Mar 23, 2026
2ff44e7
Fix symbol collisions
jacobsimionato Mar 23, 2026
7594531
Fix lints etc
jacobsimionato Mar 23, 2026
5c430dc
fix typing etc
jacobsimionato Mar 23, 2026
c183af3
Action feedback
jacobsimionato Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ a2a_agents/python/a2ui_agent/src/a2ui/assets/**/*.json
agent_sdks/python/src/a2ui/assets/**/*.json
## Generated JS file from the strictly-typed `sandbox.ts`.
samples/client/angular/projects/mcp_calculator/public/sandbox_iframe/sandbox.js
*.tsbuildinfo
64 changes: 62 additions & 2 deletions renderers/react/a2ui_explorer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import {useState, useEffect, useSyncExternalStore, useCallback} from 'react';
import {MessageProcessor, SurfaceModel, type A2uiMessage} from '@a2ui/web_core/v0_9';
import {minimalCatalog, A2uiSurface, type ReactComponentImplementation} from '@a2ui/react/v0_9';
import {minimalCatalog, basicCatalog, A2uiSurface, type ReactComponentImplementation} from '@a2ui/react/v0_9';

// Import Minimal examples
import min1 from '../../../../specification/v0_9/json/catalogs/minimal/examples/1_simple_text.json';
Expand All @@ -27,6 +27,37 @@ import min5 from '../../../../specification/v0_9/json/catalogs/minimal/examples/
import min6 from '../../../../specification/v0_9/json/catalogs/minimal/examples/6_capitalized_text.json';
import min7 from '../../../../specification/v0_9/json/catalogs/minimal/examples/7_incremental.json';

// Import Basic examples
import b01 from "../../../../specification/v0_9/json/catalogs/basic/examples/01_flight-status.json";
import b02 from "../../../../specification/v0_9/json/catalogs/basic/examples/02_email-compose.json";
import b03 from "../../../../specification/v0_9/json/catalogs/basic/examples/03_calendar-day.json";
import b04 from "../../../../specification/v0_9/json/catalogs/basic/examples/04_weather-current.json";
import b05 from "../../../../specification/v0_9/json/catalogs/basic/examples/05_product-card.json";
import b06 from "../../../../specification/v0_9/json/catalogs/basic/examples/06_music-player.json";
import b07 from "../../../../specification/v0_9/json/catalogs/basic/examples/07_task-card.json";
import b08 from "../../../../specification/v0_9/json/catalogs/basic/examples/08_user-profile.json";
import b09 from "../../../../specification/v0_9/json/catalogs/basic/examples/09_login-form.json";
import b10 from "../../../../specification/v0_9/json/catalogs/basic/examples/10_notification-permission.json";
import b11 from "../../../../specification/v0_9/json/catalogs/basic/examples/11_purchase-complete.json";
import b12 from "../../../../specification/v0_9/json/catalogs/basic/examples/12_chat-message.json";
import b13 from "../../../../specification/v0_9/json/catalogs/basic/examples/13_coffee-order.json";
import b14 from "../../../../specification/v0_9/json/catalogs/basic/examples/14_sports-player.json";
import b15 from "../../../../specification/v0_9/json/catalogs/basic/examples/15_account-balance.json";
import b16 from "../../../../specification/v0_9/json/catalogs/basic/examples/16_workout-summary.json";
import b17 from "../../../../specification/v0_9/json/catalogs/basic/examples/17_event-detail.json";
import b18 from "../../../../specification/v0_9/json/catalogs/basic/examples/18_track-list.json";
import b19 from "../../../../specification/v0_9/json/catalogs/basic/examples/19_software-purchase.json";
import b20 from "../../../../specification/v0_9/json/catalogs/basic/examples/20_restaurant-card.json";
import b21 from "../../../../specification/v0_9/json/catalogs/basic/examples/21_shipping-status.json";
import b22 from "../../../../specification/v0_9/json/catalogs/basic/examples/22_credit-card.json";
import b23 from "../../../../specification/v0_9/json/catalogs/basic/examples/23_step-counter.json";
import b24 from "../../../../specification/v0_9/json/catalogs/basic/examples/24_recipe-card.json";
import b25 from "../../../../specification/v0_9/json/catalogs/basic/examples/25_contact-card.json";
import b26 from "../../../../specification/v0_9/json/catalogs/basic/examples/26_podcast-episode.json";
import b27 from "../../../../specification/v0_9/json/catalogs/basic/examples/27_stats-card.json";
import b28 from "../../../../specification/v0_9/json/catalogs/basic/examples/28_countdown-timer.json";
import b29 from "../../../../specification/v0_9/json/catalogs/basic/examples/29_movie-card.json";

const exampleFiles = [
{key: 'min1', data: min1, catalog: 'Minimal'},
{key: 'min2', data: min2, catalog: 'Minimal'},
Expand All @@ -35,6 +66,35 @@ const exampleFiles = [
{key: 'min5', data: min5, catalog: 'Minimal'},
{key: 'min6', data: min6, catalog: 'Minimal'},
{key: 'min7', data: min7, catalog: 'Minimal'},
{ key: 'b01', data: b01, catalog: 'Basic' },
{ key: 'b02', data: b02, catalog: 'Basic' },
{ key: 'b03', data: b03, catalog: 'Basic' },
{ key: 'b04', data: b04, catalog: 'Basic' },
{ key: 'b05', data: b05, catalog: 'Basic' },
{ key: 'b06', data: b06, catalog: 'Basic' },
{ key: 'b07', data: b07, catalog: 'Basic' },
{ key: 'b08', data: b08, catalog: 'Basic' },
{ key: 'b09', data: b09, catalog: 'Basic' },
{ key: 'b10', data: b10, catalog: 'Basic' },
{ key: 'b11', data: b11, catalog: 'Basic' },
{ key: 'b12', data: b12, catalog: 'Basic' },
{ key: 'b13', data: b13, catalog: 'Basic' },
{ key: 'b14', data: b14, catalog: 'Basic' },
{ key: 'b15', data: b15, catalog: 'Basic' },
{ key: 'b16', data: b16, catalog: 'Basic' },
{ key: 'b17', data: b17, catalog: 'Basic' },
{ key: 'b18', data: b18, catalog: 'Basic' },
{ key: 'b19', data: b19, catalog: 'Basic' },
{ key: 'b20', data: b20, catalog: 'Basic' },
{ key: 'b21', data: b21, catalog: 'Basic' },
{ key: 'b22', data: b22, catalog: 'Basic' },
{ key: 'b23', data: b23, catalog: 'Basic' },
{ key: 'b24', data: b24, catalog: 'Basic' },
{ key: 'b25', data: b25, catalog: 'Basic' },
{ key: 'b26', data: b26, catalog: 'Basic' },
{ key: 'b27', data: b27, catalog: 'Basic' },
{ key: 'b28', data: b28, catalog: 'Basic' },
{ key: 'b29', data: b29, catalog: 'Basic' },
];

const getMessages = (ex: { messages: A2uiMessage[] } | A2uiMessage[] | undefined) => (Array.isArray(ex) ? ex : ex?.messages);
Expand Down Expand Up @@ -78,7 +138,7 @@ export default function App() {
if (prevProcessor) {
prevProcessor.model.dispose();
}
const newProcessor = new MessageProcessor([minimalCatalog], async (action: any) => {
const newProcessor = new MessageProcessor<ReactComponentImplementation>([minimalCatalog, basicCatalog], async (action: any) => {
setLogs((l) => [...l, {time: new Date().toISOString(), action}]);
});

Expand Down
4 changes: 3 additions & 1 deletion renderers/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@
"dependencies": {
"@a2ui/web_core": "file:../web_core",
"clsx": "^2.1.0",
"markdown-it": "^14.0.0"
"markdown-it": "^14.0.0",
"zod": "^3.23.8",
"rxjs": "^7.8.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
Expand Down
36 changes: 36 additions & 0 deletions renderers/react/src/v0_9/catalog/basic/components/AudioPlayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import {createReactComponent} from '../../../adapter';
import {AudioPlayerApi} from '@a2ui/web_core/v0_9/basic_catalog';
import {getBaseLeafStyle} from '../utils';

export const AudioPlayer = createReactComponent(AudioPlayerApi, ({props}) => {
const style: React.CSSProperties = {
...getBaseLeafStyle(),
width: '100%',
};

return (
<div style={{display: 'flex', flexDirection: 'column', gap: '4px', width: '100%'}}>
{props.description && (
<span style={{fontSize: '12px', color: '#666'}}>{props.description}</span>
)}
<audio src={props.url} controls style={style} />
</div>
);
});
47 changes: 47 additions & 0 deletions renderers/react/src/v0_9/catalog/basic/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import {createReactComponent} from '../../../adapter';
import {ButtonApi} from '@a2ui/web_core/v0_9/basic_catalog';
import {LEAF_MARGIN} from '../utils';

export const Button = createReactComponent(ButtonApi, ({props, buildChild}) => {
const style: React.CSSProperties = {
margin: LEAF_MARGIN,
padding: '8px 16px',
cursor: 'pointer',
border: props.variant === 'borderless' ? 'none' : '1px solid #ccc',
backgroundColor:
props.variant === 'primary'
? 'var(--a2ui-primary-color, #007bff)'
: props.variant === 'borderless'
? 'transparent'
: '#fff',
color: props.variant === 'primary' ? '#fff' : 'inherit',
borderRadius: '4px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
boxSizing: 'border-box',
};

return (
<button style={style} onClick={props.action} disabled={props.isValid === false}>
{props.child ? buildChild(props.child) : null}
</button>
);
});
31 changes: 31 additions & 0 deletions renderers/react/src/v0_9/catalog/basic/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import {createReactComponent} from '../../../adapter';
import {CardApi} from '@a2ui/web_core/v0_9/basic_catalog';
import {getBaseContainerStyle} from '../utils';

export const Card = createReactComponent(CardApi, ({props, buildChild}) => {
const style: React.CSSProperties = {
...getBaseContainerStyle(),
backgroundColor: '#fff',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
width: '100%',
};

return <div style={style}>{props.child ? buildChild(props.child) : null}</div>;
});
57 changes: 57 additions & 0 deletions renderers/react/src/v0_9/catalog/basic/components/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import {createReactComponent} from '../../../adapter';
import {CheckBoxApi} from '@a2ui/web_core/v0_9/basic_catalog';
import {LEAF_MARGIN} from '../utils';

export const CheckBox = createReactComponent(CheckBoxApi, ({props}) => {
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
props.setValue(e.target.checked);
};

const uniqueId = React.useId();

const hasError = props.validationErrors && props.validationErrors.length > 0;

return (
<div style={{display: 'flex', flexDirection: 'column', margin: LEAF_MARGIN}}>
<div style={{display: 'flex', alignItems: 'center', gap: '8px'}}>
<input
id={uniqueId}
type="checkbox"
checked={!!props.value}
onChange={onChange}
style={{cursor: 'pointer', outline: hasError ? '1px solid red' : 'none'}}
/>
{props.label && (
<label
htmlFor={uniqueId}
style={{cursor: 'pointer', color: hasError ? 'red' : 'inherit'}}
>
{props.label}
</label>
)}
</div>
{hasError && (
<span style={{fontSize: '12px', color: 'red', marginTop: '4px'}}>
{props.validationErrors[0]}
</span>
)}
</div>
);
});
49 changes: 49 additions & 0 deletions renderers/react/src/v0_9/catalog/basic/components/ChildList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import {type ComponentContext} from '@a2ui/web_core/v0_9';

export const ChildList: React.FC<{
childList: unknown;
context: ComponentContext;
buildChild: (id: string, basePath?: string) => React.ReactNode;
}> = ({childList, buildChild}) => {
if (Array.isArray(childList)) {
return (
<>
{childList.map((item: unknown, i: number) => {
// The new binder outputs objects like { id: string, basePath: string } for arrays of structural nodes
if (item && typeof item === 'object' && 'id' in item) {
const node = item as {id: string; basePath?: string};
return (
<React.Fragment key={`${node.id}-${i}`}>
{buildChild(node.id, node.basePath)}
</React.Fragment>
);
}
// Fallback for static string lists
if (typeof item === 'string') {
return <React.Fragment key={`${item}-${i}`}>{buildChild(item)}</React.Fragment>;
}
return null;
})}
</>
);
}

return null;
};
Loading
Loading