@easyops-cn/a2ui-react 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/speckit.analyze.md +184 -0
- package/.claude/commands/speckit.checklist.md +294 -0
- package/.claude/commands/speckit.clarify.md +181 -0
- package/.claude/commands/speckit.constitution.md +82 -0
- package/.claude/commands/speckit.implement.md +135 -0
- package/.claude/commands/speckit.plan.md +89 -0
- package/.claude/commands/speckit.specify.md +256 -0
- package/.claude/commands/speckit.tasks.md +137 -0
- package/.claude/commands/speckit.taskstoissues.md +30 -0
- package/.github/workflows/deploy.yml +69 -0
- package/.husky/pre-commit +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc +7 -0
- package/.specify/memory/constitution.md +73 -0
- package/.specify/scripts/bash/check-prerequisites.sh +166 -0
- package/.specify/scripts/bash/common.sh +156 -0
- package/.specify/scripts/bash/create-new-feature.sh +297 -0
- package/.specify/scripts/bash/setup-plan.sh +61 -0
- package/.specify/scripts/bash/update-agent-context.sh +799 -0
- package/.specify/templates/agent-file-template.md +28 -0
- package/.specify/templates/checklist-template.md +40 -0
- package/.specify/templates/plan-template.md +105 -0
- package/.specify/templates/spec-template.md +115 -0
- package/.specify/templates/tasks-template.md +250 -0
- package/CLAUDE.md +105 -0
- package/CONTRIBUTING.md +97 -0
- package/README.md +126 -0
- package/components.json +21 -0
- package/eslint.config.js +25 -0
- package/netlify.toml +50 -0
- package/package.json +94 -0
- package/playground/README.md +75 -0
- package/playground/index.html +22 -0
- package/playground/package.json +32 -0
- package/playground/public/favicon.svg +8 -0
- package/playground/src/App.css +256 -0
- package/playground/src/App.tsx +115 -0
- package/playground/src/assets/react.svg +1 -0
- package/playground/src/components/ErrorDisplay.tsx +13 -0
- package/playground/src/components/ExampleSelector.tsx +64 -0
- package/playground/src/components/Header.tsx +47 -0
- package/playground/src/components/JsonEditor.tsx +32 -0
- package/playground/src/components/Preview.tsx +78 -0
- package/playground/src/components/ThemeToggle.tsx +19 -0
- package/playground/src/data/examples.ts +1571 -0
- package/playground/src/hooks/useTheme.ts +55 -0
- package/playground/src/index.css +220 -0
- package/playground/src/main.tsx +10 -0
- package/playground/tsconfig.app.json +34 -0
- package/playground/tsconfig.json +13 -0
- package/playground/tsconfig.node.json +26 -0
- package/playground/vite.config.ts +31 -0
- package/specs/001-a2ui-renderer/checklists/requirements.md +41 -0
- package/specs/001-a2ui-renderer/data-model.md +140 -0
- package/specs/001-a2ui-renderer/plan.md +123 -0
- package/specs/001-a2ui-renderer/quickstart.md +141 -0
- package/specs/001-a2ui-renderer/research.md +140 -0
- package/specs/001-a2ui-renderer/spec.md +165 -0
- package/specs/001-a2ui-renderer/tasks.md +310 -0
- package/specs/002-playground/checklists/requirements.md +37 -0
- package/specs/002-playground/contracts/components.md +120 -0
- package/specs/002-playground/data-model.md +149 -0
- package/specs/002-playground/plan.md +73 -0
- package/specs/002-playground/quickstart.md +158 -0
- package/specs/002-playground/research.md +117 -0
- package/specs/002-playground/spec.md +109 -0
- package/specs/002-playground/tasks.md +224 -0
- package/src/0.8/A2UIRender.test.tsx +793 -0
- package/src/0.8/A2UIRender.tsx +142 -0
- package/src/0.8/components/ComponentRenderer.test.tsx +373 -0
- package/src/0.8/components/ComponentRenderer.tsx +163 -0
- package/src/0.8/components/UnknownComponent.tsx +49 -0
- package/src/0.8/components/display/AudioPlayerComponent.tsx +37 -0
- package/src/0.8/components/display/DividerComponent.tsx +23 -0
- package/src/0.8/components/display/IconComponent.tsx +137 -0
- package/src/0.8/components/display/ImageComponent.tsx +57 -0
- package/src/0.8/components/display/TextComponent.tsx +56 -0
- package/src/0.8/components/display/VideoComponent.tsx +31 -0
- package/src/0.8/components/display/display.test.tsx +660 -0
- package/src/0.8/components/display/index.ts +10 -0
- package/src/0.8/components/index.ts +14 -0
- package/src/0.8/components/interactive/ButtonComponent.tsx +44 -0
- package/src/0.8/components/interactive/CheckBoxComponent.tsx +45 -0
- package/src/0.8/components/interactive/DateTimeInputComponent.tsx +176 -0
- package/src/0.8/components/interactive/MultipleChoiceComponent.tsx +157 -0
- package/src/0.8/components/interactive/SliderComponent.tsx +53 -0
- package/src/0.8/components/interactive/TextFieldComponent.tsx +65 -0
- package/src/0.8/components/interactive/index.ts +10 -0
- package/src/0.8/components/interactive/interactive.test.tsx +618 -0
- package/src/0.8/components/layout/CardComponent.tsx +30 -0
- package/src/0.8/components/layout/ColumnComponent.tsx +93 -0
- package/src/0.8/components/layout/ListComponent.tsx +81 -0
- package/src/0.8/components/layout/ModalComponent.tsx +41 -0
- package/src/0.8/components/layout/RowComponent.tsx +94 -0
- package/src/0.8/components/layout/TabsComponent.tsx +59 -0
- package/src/0.8/components/layout/index.ts +10 -0
- package/src/0.8/components/layout/layout.test.tsx +558 -0
- package/src/0.8/contexts/A2UIProvider.test.tsx +226 -0
- package/src/0.8/contexts/A2UIProvider.tsx +54 -0
- package/src/0.8/contexts/ActionContext.test.tsx +242 -0
- package/src/0.8/contexts/ActionContext.tsx +105 -0
- package/src/0.8/contexts/ComponentsMapContext.tsx +125 -0
- package/src/0.8/contexts/DataModelContext.test.tsx +335 -0
- package/src/0.8/contexts/DataModelContext.tsx +184 -0
- package/src/0.8/contexts/SurfaceContext.test.tsx +339 -0
- package/src/0.8/contexts/SurfaceContext.tsx +197 -0
- package/src/0.8/hooks/useA2UIMessageHandler.test.tsx +399 -0
- package/src/0.8/hooks/useA2UIMessageHandler.ts +123 -0
- package/src/0.8/hooks/useComponent.test.tsx +148 -0
- package/src/0.8/hooks/useComponent.ts +39 -0
- package/src/0.8/hooks/useDataBinding.test.tsx +334 -0
- package/src/0.8/hooks/useDataBinding.ts +99 -0
- package/src/0.8/hooks/useDispatchAction.test.tsx +83 -0
- package/src/0.8/hooks/useDispatchAction.ts +35 -0
- package/src/0.8/hooks/useSurface.test.tsx +114 -0
- package/src/0.8/hooks/useSurface.ts +34 -0
- package/src/0.8/index.ts +38 -0
- package/src/0.8/schemas/client_to_server.json +50 -0
- package/src/0.8/schemas/server_to_client.json +148 -0
- package/src/0.8/schemas/standard_catalog_definition.json +661 -0
- package/src/0.8/types/index.ts +448 -0
- package/src/0.8/utils/dataBinding.test.ts +443 -0
- package/src/0.8/utils/dataBinding.ts +212 -0
- package/src/0.8/utils/pathUtils.test.ts +353 -0
- package/src/0.8/utils/pathUtils.ts +200 -0
- package/src/components/ui/button.tsx +62 -0
- package/src/components/ui/calendar.tsx +220 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/dialog.tsx +141 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/native-select.tsx +53 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/select.tsx +188 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/slider.tsx +61 -0
- package/src/components/ui/tabs.tsx +64 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/index.ts +1 -0
- package/src/lib/utils.ts +6 -0
- package/tsconfig.json +28 -0
- package/vite.config.ts +29 -0
- package/vitest.config.ts +22 -0
- package/vitest.setup.ts +8 -0
- package/website/README.md +4 -0
- package/website/assets/favicon.svg +8 -0
- package/website/content/.gitkeep +0 -0
- package/website/content/index.md +122 -0
- package/website/global.d.ts +9 -0
- package/website/package.json +17 -0
- package/website/plain.config.js +28 -0
- package/website/serve.json +6 -0
- package/website/src/client/color-mode-switch.css +47 -0
- package/website/src/client/index.js +61 -0
- package/website/src/client/moon.svg +1 -0
- package/website/src/client/sun.svg +1 -0
- package/website/src/components/Footer.jsx +9 -0
- package/website/src/components/Header.jsx +44 -0
- package/website/src/components/Page.jsx +28 -0
- package/website/src/global.css +423 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Implementation Plan: A2UIRenderer Component Library
|
|
2
|
+
|
|
3
|
+
**Branch**: `001-a2ui-renderer` | **Date**: 2026-01-10 | **Spec**: [spec.md](./spec.md)
|
|
4
|
+
**Input**: Feature specification from `/specs/001-a2ui-renderer/spec.md`
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
Implement the A2UIRender component and public API exports for the A2UI React renderer library. The library already has substantial infrastructure implemented (contexts, hooks, component registry, default components). The remaining work focuses on:
|
|
9
|
+
|
|
10
|
+
1. Creating the main `A2UIRender` component that matches the README.md API
|
|
11
|
+
2. Setting up the versioned export path (`@easyops-cn/a2ui-react/0.8`)
|
|
12
|
+
3. Adding development-mode placeholder for unknown components
|
|
13
|
+
4. Ensuring all public types and hooks are properly exported
|
|
14
|
+
|
|
15
|
+
## Technical Context
|
|
16
|
+
|
|
17
|
+
**Language/Version**: TypeScript 5.9, React 19
|
|
18
|
+
**Primary Dependencies**: React 19, Radix UI (for UI primitives), Tailwind CSS (via class-variance-authority)
|
|
19
|
+
**Storage**: N/A (client-side rendering library)
|
|
20
|
+
**Testing**: Vitest with @testing-library/react
|
|
21
|
+
**Target Platform**: Browser (ES2020+)
|
|
22
|
+
**Project Type**: Single library project
|
|
23
|
+
**Performance Goals**: <100ms action callback latency, 10+ levels nested rendering
|
|
24
|
+
**Constraints**: Must work with React 18+ (peer dependency allows ^19.0.0)
|
|
25
|
+
**Scale/Scope**: ~20 component types, single entry point
|
|
26
|
+
|
|
27
|
+
## Constitution Check
|
|
28
|
+
|
|
29
|
+
_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._
|
|
30
|
+
|
|
31
|
+
The constitution template is not yet customized for this project. Proceeding with standard library development practices:
|
|
32
|
+
|
|
33
|
+
- [x] Library-first: Self-contained, independently testable
|
|
34
|
+
- [x] Test coverage: Vitest tests exist for contexts, hooks, and components
|
|
35
|
+
- [x] Type safety: Full TypeScript with strict mode
|
|
36
|
+
- [x] Documentation: JSDoc comments on all public APIs
|
|
37
|
+
|
|
38
|
+
## Existing Implementation Analysis
|
|
39
|
+
|
|
40
|
+
### Already Implemented
|
|
41
|
+
|
|
42
|
+
| Component/Module | Status | Location |
|
|
43
|
+
| ----------------------- | ----------- | ------------------------------------------------------------------ |
|
|
44
|
+
| **Contexts** | ✅ Complete | `src/0.8/contexts/` |
|
|
45
|
+
| - A2UIProvider | ✅ | Combines Surface, DataModel, Action providers |
|
|
46
|
+
| - SurfaceContext | ✅ | Surface state management |
|
|
47
|
+
| - DataModelContext | ✅ | Data model state management |
|
|
48
|
+
| - ActionContext | ✅ | Action dispatching |
|
|
49
|
+
| **Hooks** | ✅ Complete | `src/0.8/hooks/` |
|
|
50
|
+
| - useDataBinding | ✅ | Read-only data binding |
|
|
51
|
+
| - useFormBinding | ✅ | Two-way form binding |
|
|
52
|
+
| - useDispatchAction | ✅ | Action dispatching |
|
|
53
|
+
| - useComponent | ✅ | Component lookup |
|
|
54
|
+
| - useSurface | ✅ | Surface lookup |
|
|
55
|
+
| - useA2UIMessageHandler | ✅ | Message processing |
|
|
56
|
+
| **Components** | ✅ Complete | `src/0.8/components/` |
|
|
57
|
+
| - ComponentRenderer | ✅ | Component routing with registry |
|
|
58
|
+
| - Display (6 types) | ✅ | Text, Image, Icon, Video, AudioPlayer, Divider |
|
|
59
|
+
| - Layout (6 types) | ✅ | Row, Column, List, Card, Tabs, Modal |
|
|
60
|
+
| - Interactive (6 types) | ✅ | Button, CheckBox, TextField, DateTimeInput, MultipleChoice, Slider |
|
|
61
|
+
| **Types** | ✅ Complete | `src/0.8/types/index.ts` |
|
|
62
|
+
| **Utilities** | ✅ Complete | `src/0.8/utils/` |
|
|
63
|
+
|
|
64
|
+
### Not Yet Implemented
|
|
65
|
+
|
|
66
|
+
| Component/Module | Status | Required For |
|
|
67
|
+
| ------------------------- | ---------- | ------------------------------------------- |
|
|
68
|
+
| **A2UIRender** | ❌ Missing | Main entry component per README.md |
|
|
69
|
+
| **index.ts exports** | ❌ Missing | Versioned path `@easyops-cn/a2ui-react/0.8` |
|
|
70
|
+
| **Dev-mode placeholder** | ❌ Missing | Unknown component handling per spec |
|
|
71
|
+
| **ComponentsMap support** | ❌ Missing | Custom component override per README.md |
|
|
72
|
+
|
|
73
|
+
## Project Structure
|
|
74
|
+
|
|
75
|
+
### Documentation (this feature)
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
specs/001-a2ui-renderer/
|
|
79
|
+
├── plan.md # This file
|
|
80
|
+
├── research.md # Phase 0 output
|
|
81
|
+
├── data-model.md # Phase 1 output
|
|
82
|
+
├── quickstart.md # Phase 1 output
|
|
83
|
+
├── contracts/ # Phase 1 output (N/A - no API contracts)
|
|
84
|
+
└── tasks.md # Phase 2 output
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Source Code (repository root)
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
src/
|
|
91
|
+
├── index.ts # Root export (existing)
|
|
92
|
+
└── 0.8/
|
|
93
|
+
├── index.ts # NEW: Versioned public API exports
|
|
94
|
+
├── A2UIRender.tsx # NEW: Main render component
|
|
95
|
+
├── A2UIRender.test.tsx # NEW: Tests for A2UIRender
|
|
96
|
+
├── contexts/ # Existing: Context providers
|
|
97
|
+
│ ├── A2UIProvider.tsx
|
|
98
|
+
│ ├── ActionContext.tsx
|
|
99
|
+
│ ├── DataModelContext.tsx
|
|
100
|
+
│ └── SurfaceContext.tsx
|
|
101
|
+
├── hooks/ # Existing: React hooks
|
|
102
|
+
│ ├── useDataBinding.ts
|
|
103
|
+
│ ├── useDispatchAction.ts
|
|
104
|
+
│ ├── useComponent.ts
|
|
105
|
+
│ ├── useSurface.ts
|
|
106
|
+
│ └── useA2UIMessageHandler.ts
|
|
107
|
+
├── components/ # Existing: Component implementations
|
|
108
|
+
│ ├── ComponentRenderer.tsx
|
|
109
|
+
│ ├── display/
|
|
110
|
+
│ ├── layout/
|
|
111
|
+
│ └── interactive/
|
|
112
|
+
├── types/ # Existing: Type definitions
|
|
113
|
+
│ └── index.ts
|
|
114
|
+
└── utils/ # Existing: Utility functions
|
|
115
|
+
├── dataBinding.ts
|
|
116
|
+
└── pathUtils.ts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Structure Decision**: Single library project structure. The existing codebase follows a well-organized module structure under `src/0.8/`. New files will be added at the same level.
|
|
120
|
+
|
|
121
|
+
## Complexity Tracking
|
|
122
|
+
|
|
123
|
+
No constitution violations. The implementation follows the existing patterns and adds minimal new code.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Quickstart: A2UIRenderer Component Library
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-01-10
|
|
4
|
+
**Feature**: 001-a2ui-renderer
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @easyops-cn/a2ui-react
|
|
10
|
+
# or
|
|
11
|
+
pnpm add @easyops-cn/a2ui-react
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Basic Usage
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { A2UIRender, A2UIMessage, A2UIAction } from '@easyops-cn/a2ui-react/0.8'
|
|
18
|
+
|
|
19
|
+
function App() {
|
|
20
|
+
const messages: A2UIMessage[] = [
|
|
21
|
+
// Messages from your A2UI server
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const handleAction = (action: A2UIAction) => {
|
|
25
|
+
console.log('Action received:', action)
|
|
26
|
+
// Handle the action (e.g., send to server)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <A2UIRender messages={messages} onAction={handleAction} />
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Custom Components
|
|
34
|
+
|
|
35
|
+
Override default components or add new ones:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import {
|
|
39
|
+
A2UIRender,
|
|
40
|
+
useDispatchAction,
|
|
41
|
+
ComponentRenderer,
|
|
42
|
+
} from '@easyops-cn/a2ui-react/0.8'
|
|
43
|
+
|
|
44
|
+
// Custom Button component
|
|
45
|
+
function CustomButton({ surfaceId, componentId, child, action }) {
|
|
46
|
+
const dispatchAction = useDispatchAction()
|
|
47
|
+
|
|
48
|
+
const handleClick = () => {
|
|
49
|
+
if (action) {
|
|
50
|
+
dispatchAction(surfaceId, componentId, action)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<button className="my-custom-button" onClick={handleClick}>
|
|
56
|
+
<ComponentRenderer surfaceId={surfaceId} componentId={child} />
|
|
57
|
+
</button>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Register custom components
|
|
62
|
+
const ComponentsMap = new Map([['Button', CustomButton]])
|
|
63
|
+
|
|
64
|
+
function App() {
|
|
65
|
+
return (
|
|
66
|
+
<A2UIRender
|
|
67
|
+
components={ComponentsMap}
|
|
68
|
+
messages={messages}
|
|
69
|
+
onAction={handleAction}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Data Binding in Custom Components
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { useDataBinding, useFormBinding } from '@easyops-cn/a2ui-react/0.8'
|
|
79
|
+
|
|
80
|
+
// Read-only binding
|
|
81
|
+
function DisplayComponent({ surfaceId, text }) {
|
|
82
|
+
const textValue = useDataBinding<string>(surfaceId, text, '')
|
|
83
|
+
return <span>{textValue}</span>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Two-way binding for forms
|
|
87
|
+
function InputComponent({ surfaceId, value }) {
|
|
88
|
+
const [inputValue, setInputValue] = useFormBinding<string>(
|
|
89
|
+
surfaceId,
|
|
90
|
+
value,
|
|
91
|
+
''
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API Reference
|
|
101
|
+
|
|
102
|
+
### A2UIRender Props
|
|
103
|
+
|
|
104
|
+
| Prop | Type | Required | Description |
|
|
105
|
+
| ------------ | ------------------------------ | -------- | ------------------------------------- |
|
|
106
|
+
| `messages` | `A2UIMessage[]` | Yes | Array of A2UI messages to render |
|
|
107
|
+
| `onAction` | `(action: A2UIAction) => void` | No | Callback when user triggers an action |
|
|
108
|
+
| `components` | `Map<string, ComponentType>` | No | Custom component overrides |
|
|
109
|
+
|
|
110
|
+
### Hooks
|
|
111
|
+
|
|
112
|
+
| Hook | Signature | Description |
|
|
113
|
+
| ------------------- | --------------------------------------------------------- | --------------------------------------- |
|
|
114
|
+
| `useDispatchAction` | `() => (surfaceId, componentId, action) => void` | Dispatch actions from custom components |
|
|
115
|
+
| `useDataBinding` | `<T>(surfaceId, source, default?) => T` | Read data from the data model |
|
|
116
|
+
| `useFormBinding` | `<T>(surfaceId, source, default?) => [T, (v: T) => void]` | Two-way data binding |
|
|
117
|
+
|
|
118
|
+
### Types
|
|
119
|
+
|
|
120
|
+
| Type | Description |
|
|
121
|
+
| ------------- | ------------------------------------------ |
|
|
122
|
+
| `A2UIMessage` | Server-to-client message |
|
|
123
|
+
| `A2UIAction` | Action payload sent to onAction callback |
|
|
124
|
+
| `Action` | Action definition in component props |
|
|
125
|
+
| `ValueSource` | Literal value or data model path reference |
|
|
126
|
+
|
|
127
|
+
## Message Flow
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Server Client (A2UIRender)
|
|
131
|
+
│ │
|
|
132
|
+
│──── beginRendering ───────────────►│ Initialize surface
|
|
133
|
+
│ │
|
|
134
|
+
│──── surfaceUpdate ────────────────►│ Add/update components
|
|
135
|
+
│ │
|
|
136
|
+
│──── dataModelUpdate ──────────────►│ Update data model
|
|
137
|
+
│ │
|
|
138
|
+
│◄─── ActionPayload ────────────────│ User interaction
|
|
139
|
+
│ │
|
|
140
|
+
│──── deleteSurface ────────────────►│ Remove surface
|
|
141
|
+
```
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Research: A2UIRenderer Component Library
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-01-10
|
|
4
|
+
**Feature**: 001-a2ui-renderer
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
This document captures research findings for implementing the A2UIRender component and public API exports.
|
|
9
|
+
|
|
10
|
+
## Decision 1: A2UIRender Component Architecture
|
|
11
|
+
|
|
12
|
+
**Decision**: Compose A2UIRender as a thin wrapper around existing infrastructure
|
|
13
|
+
|
|
14
|
+
**Rationale**:
|
|
15
|
+
|
|
16
|
+
- The existing `A2UIProvider` already combines all necessary context providers
|
|
17
|
+
- The existing `useA2UIMessageHandler` hook handles message processing
|
|
18
|
+
- The existing `ComponentRenderer` handles component routing
|
|
19
|
+
- A2UIRender only needs to:
|
|
20
|
+
1. Accept `messages`, `onAction`, and optional `components` props
|
|
21
|
+
2. Wrap children in A2UIProvider
|
|
22
|
+
3. Process messages and render surfaces
|
|
23
|
+
|
|
24
|
+
**Alternatives considered**:
|
|
25
|
+
|
|
26
|
+
- Rewrite from scratch: Rejected - would duplicate existing tested code
|
|
27
|
+
- Extend A2UIProvider: Rejected - A2UIProvider is a context provider, not a renderer
|
|
28
|
+
|
|
29
|
+
## Decision 2: ComponentsMap Implementation
|
|
30
|
+
|
|
31
|
+
**Decision**: Use React Context to pass custom components to ComponentRenderer
|
|
32
|
+
|
|
33
|
+
**Rationale**:
|
|
34
|
+
|
|
35
|
+
- The README.md shows `components` prop as `Map<string, React.ComponentType<any>>`
|
|
36
|
+
- ComponentRenderer already has a `componentRegistry` object
|
|
37
|
+
- Need to merge custom components with defaults at render time
|
|
38
|
+
- Context allows deep component tree access without prop drilling
|
|
39
|
+
|
|
40
|
+
**Alternatives considered**:
|
|
41
|
+
|
|
42
|
+
- Global registry mutation: Rejected - not React-friendly, causes side effects
|
|
43
|
+
- Prop drilling: Rejected - would require changes to all container components
|
|
44
|
+
- Module-level configuration: Rejected - not compatible with multiple A2UIRender instances
|
|
45
|
+
|
|
46
|
+
## Decision 3: Unknown Component Handling
|
|
47
|
+
|
|
48
|
+
**Decision**: Use `process.env.NODE_ENV` to switch between dev placeholder and production skip
|
|
49
|
+
|
|
50
|
+
**Rationale**:
|
|
51
|
+
|
|
52
|
+
- Spec requires: "Render placeholder in development, skip in production"
|
|
53
|
+
- Standard React pattern for dev-only features
|
|
54
|
+
- Vite/bundlers automatically replace `process.env.NODE_ENV`
|
|
55
|
+
- No runtime overhead in production builds
|
|
56
|
+
|
|
57
|
+
**Alternatives considered**:
|
|
58
|
+
|
|
59
|
+
- Runtime configuration prop: Rejected - adds API complexity
|
|
60
|
+
- Always show placeholder: Rejected - spec requires production skip
|
|
61
|
+
- Always skip: Rejected - spec requires dev visibility
|
|
62
|
+
|
|
63
|
+
## Decision 4: Export Structure for Versioned Path
|
|
64
|
+
|
|
65
|
+
**Decision**: Create `src/0.8/index.ts` as the entry point for `@easyops-cn/a2ui-react/0.8`
|
|
66
|
+
|
|
67
|
+
**Rationale**:
|
|
68
|
+
|
|
69
|
+
- package.json already defines: `"./0.8": { "default": "./dist/src/0.8/index.js" }`
|
|
70
|
+
- Single file makes it clear what's public API
|
|
71
|
+
- Re-exports from internal modules maintain encapsulation
|
|
72
|
+
- Matches README.md import pattern
|
|
73
|
+
|
|
74
|
+
**Alternatives considered**:
|
|
75
|
+
|
|
76
|
+
- index.ts in 0.8 folder: Rejected - index.ts is already configured in package.json
|
|
77
|
+
- Barrel exports from each module: Rejected - harder to control public surface
|
|
78
|
+
|
|
79
|
+
## Decision 5: Surface Rendering Strategy
|
|
80
|
+
|
|
81
|
+
**Decision**: Render all surfaces from the surfaces Map, each with its root component
|
|
82
|
+
|
|
83
|
+
**Rationale**:
|
|
84
|
+
|
|
85
|
+
- Messages can create multiple surfaces
|
|
86
|
+
- Each surface has its own `root` component ID
|
|
87
|
+
- SurfaceContext already maintains `Map<string, Surface>`
|
|
88
|
+
- A2UIRender should render all active surfaces
|
|
89
|
+
|
|
90
|
+
**Alternatives considered**:
|
|
91
|
+
|
|
92
|
+
- Single surface only: Rejected - A2UI protocol supports multiple surfaces
|
|
93
|
+
- Surface selection prop: Could be added later if needed
|
|
94
|
+
|
|
95
|
+
## Technical Findings
|
|
96
|
+
|
|
97
|
+
### Existing Hook Signatures (from codebase)
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// useDataBinding - matches spec FR-012
|
|
101
|
+
function useDataBinding<T>(
|
|
102
|
+
surfaceId: string,
|
|
103
|
+
source: ValueSource | undefined,
|
|
104
|
+
defaultValue?: T
|
|
105
|
+
): T
|
|
106
|
+
|
|
107
|
+
// useFormBinding - matches spec FR-013
|
|
108
|
+
function useFormBinding<T>(
|
|
109
|
+
surfaceId: string,
|
|
110
|
+
source: ValueSource | undefined,
|
|
111
|
+
defaultValue?: T
|
|
112
|
+
): [T, (value: T) => void]
|
|
113
|
+
|
|
114
|
+
// useDispatchAction - matches spec FR-011
|
|
115
|
+
function useDispatchAction(): (
|
|
116
|
+
surfaceId: string,
|
|
117
|
+
componentId: string,
|
|
118
|
+
action: Action
|
|
119
|
+
) => void
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Type Exports Required (from spec)
|
|
123
|
+
|
|
124
|
+
- `A2UIMessage` - ✅ exists in types/index.ts
|
|
125
|
+
- `A2UIAction` - ❌ needs alias (currently `ActionPayload`)
|
|
126
|
+
- `A2UIRender` - ❌ needs implementation
|
|
127
|
+
- `ComponentRenderer` - ✅ exists
|
|
128
|
+
- `useDispatchAction` - ✅ exists
|
|
129
|
+
- `useDataBinding` - ✅ exists
|
|
130
|
+
- `useFormBinding` - ✅ exists
|
|
131
|
+
|
|
132
|
+
### API Alignment with README.md
|
|
133
|
+
|
|
134
|
+
The README.md shows:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { A2UIRender, A2UIMessage, A2UIAction } from '@easyops-cn/a2ui-react/0.8'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Current types use `ActionPayload` instead of `A2UIAction`. The index.ts should export `ActionPayload as A2UIAction` for API consistency.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Feature Specification: A2UIRenderer Component Library
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `001-a2ui-renderer`
|
|
4
|
+
**Created**: 2026-01-10
|
|
5
|
+
**Status**: Draft
|
|
6
|
+
**Input**: User description: "按 README.md 的下游用户使用示例实现 A2UIRenderer"
|
|
7
|
+
|
|
8
|
+
## Clarifications
|
|
9
|
+
|
|
10
|
+
### Session 2026-01-10
|
|
11
|
+
|
|
12
|
+
- Q: How should unknown component types be handled? → A: Render placeholder in development, skip in production
|
|
13
|
+
- Q: Where does the A2UIMessage schema come from? → A: Define schema internally (this library owns the type definitions)
|
|
14
|
+
|
|
15
|
+
## User Scenarios & Testing _(mandatory)_
|
|
16
|
+
|
|
17
|
+
### User Story 1 - Basic Message Rendering (Priority: P1)
|
|
18
|
+
|
|
19
|
+
As a developer, I want to render A2UI messages using the A2UIRender component so that I can display dynamic UI content from A2UI protocol messages.
|
|
20
|
+
|
|
21
|
+
**Why this priority**: This is the core functionality - without basic rendering, no other features can work. It enables the fundamental use case of displaying A2UI messages.
|
|
22
|
+
|
|
23
|
+
**Independent Test**: Can be fully tested by passing an array of A2UIMessage objects to A2UIRender and verifying the UI renders correctly. Delivers immediate value by enabling basic A2UI integration.
|
|
24
|
+
|
|
25
|
+
**Acceptance Scenarios**:
|
|
26
|
+
|
|
27
|
+
1. **Given** an empty messages array, **When** A2UIRender is rendered, **Then** no UI components are displayed
|
|
28
|
+
2. **Given** a messages array with valid A2UIMessage objects, **When** A2UIRender is rendered, **Then** all message components are displayed in order
|
|
29
|
+
3. **Given** a messages array with nested components, **When** A2UIRender is rendered, **Then** nested components are rendered correctly within their parent containers
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### User Story 2 - Action Handling (Priority: P1)
|
|
34
|
+
|
|
35
|
+
As a developer, I want to receive action callbacks when users interact with components so that I can respond to user interactions and update application state.
|
|
36
|
+
|
|
37
|
+
**Why this priority**: Actions are essential for interactive UIs. Without action handling, the rendered UI would be static and non-functional.
|
|
38
|
+
|
|
39
|
+
**Independent Test**: Can be fully tested by clicking interactive components and verifying the onAction callback receives the correct A2UIAction payload.
|
|
40
|
+
|
|
41
|
+
**Acceptance Scenarios**:
|
|
42
|
+
|
|
43
|
+
1. **Given** a component with an action defined, **When** the user triggers the action (e.g., clicks a button), **Then** the onAction callback is invoked with the correct A2UIAction object
|
|
44
|
+
2. **Given** the onAction callback, **When** an action is dispatched, **Then** the callback receives surfaceId, componentId, and action payload
|
|
45
|
+
3. **Given** multiple interactive components, **When** different components are clicked, **Then** each dispatches its own unique action correctly
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### User Story 3 - Custom Component Override (Priority: P2)
|
|
50
|
+
|
|
51
|
+
As a developer, I want to override default components with custom implementations so that I can customize the look and behavior of rendered UI elements.
|
|
52
|
+
|
|
53
|
+
**Why this priority**: Customization is important for branding and specific UX requirements, but the library should work with defaults first.
|
|
54
|
+
|
|
55
|
+
**Independent Test**: Can be fully tested by providing a ComponentsMap with a custom Button component and verifying the custom component renders instead of the default.
|
|
56
|
+
|
|
57
|
+
**Acceptance Scenarios**:
|
|
58
|
+
|
|
59
|
+
1. **Given** a ComponentsMap with a custom Button component, **When** A2UIRender renders a Button, **Then** the custom Button component is used instead of the default
|
|
60
|
+
2. **Given** a ComponentsMap with multiple custom components, **When** A2UIRender renders those component types, **Then** each custom component is used appropriately
|
|
61
|
+
3. **Given** a ComponentsMap that does not override a component type, **When** A2UIRender renders that component type, **Then** the default component is used
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### User Story 4 - Custom Component Creation (Priority: P2)
|
|
66
|
+
|
|
67
|
+
As a developer, I want to add new custom component types that don't exist in the default set so that I can extend the UI capabilities beyond the built-in components.
|
|
68
|
+
|
|
69
|
+
**Why this priority**: Extensibility allows the library to support domain-specific components, but basic functionality must work first.
|
|
70
|
+
|
|
71
|
+
**Independent Test**: Can be fully tested by adding a new component type (e.g., Switch) to ComponentsMap and verifying it renders when that component type appears in messages.
|
|
72
|
+
|
|
73
|
+
**Acceptance Scenarios**:
|
|
74
|
+
|
|
75
|
+
1. **Given** a ComponentsMap with a new component type "Switch", **When** A2UIRender encounters a Switch component in messages, **Then** the custom Switch component is rendered
|
|
76
|
+
2. **Given** a custom component using useDispatchAction hook, **When** the component dispatches an action, **Then** the action flows through the onAction callback correctly
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### User Story 5 - Data Binding in Custom Components (Priority: P3)
|
|
81
|
+
|
|
82
|
+
As a developer, I want to use data binding hooks in custom components so that I can read dynamic values from the A2UI data context.
|
|
83
|
+
|
|
84
|
+
**Why this priority**: Data binding enables dynamic content, but basic rendering and actions are more fundamental.
|
|
85
|
+
|
|
86
|
+
**Independent Test**: Can be fully tested by creating a custom component that uses useDataBinding and verifying it displays the bound value.
|
|
87
|
+
|
|
88
|
+
**Acceptance Scenarios**:
|
|
89
|
+
|
|
90
|
+
1. **Given** a custom component using useDataBinding hook, **When** the component renders, **Then** it displays the bound data value
|
|
91
|
+
2. **Given** a data binding with a default value, **When** the bound path has no data, **Then** the default value is used
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### User Story 6 - Form Binding in Custom Components (Priority: P3)
|
|
96
|
+
|
|
97
|
+
As a developer, I want to use form binding hooks in custom components so that I can create two-way data binding for form inputs.
|
|
98
|
+
|
|
99
|
+
**Why this priority**: Form binding enables interactive forms, building on top of basic data binding.
|
|
100
|
+
|
|
101
|
+
**Independent Test**: Can be fully tested by creating a custom Switch component that uses useFormBinding and verifying value changes are reflected.
|
|
102
|
+
|
|
103
|
+
**Acceptance Scenarios**:
|
|
104
|
+
|
|
105
|
+
1. **Given** a custom component using useFormBinding hook, **When** the component renders, **Then** it displays the current bound value
|
|
106
|
+
2. **Given** a custom component using useFormBinding hook, **When** the user changes the value, **Then** the bound value is updated
|
|
107
|
+
3. **Given** a form binding with a default value, **When** the bound path has no data, **Then** the default value is used
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### Edge Cases
|
|
112
|
+
|
|
113
|
+
- What happens when messages array is null or undefined? System should handle gracefully without crashing
|
|
114
|
+
- What happens when a component type in messages has no matching renderer? System renders a visible placeholder in development mode to aid debugging, and silently skips the component in production mode
|
|
115
|
+
- What happens when an action is dispatched but no onAction callback is provided? System should handle gracefully
|
|
116
|
+
- What happens when useDataBinding references a non-existent path? System should return the default value
|
|
117
|
+
- What happens when ComponentsMap contains invalid component references? System should fall back to defaults or skip
|
|
118
|
+
|
|
119
|
+
## Requirements _(mandatory)_
|
|
120
|
+
|
|
121
|
+
### Functional Requirements
|
|
122
|
+
|
|
123
|
+
- **FR-001**: System MUST export A2UIRender component that accepts messages and onAction props
|
|
124
|
+
- **FR-002**: System MUST export A2UIMessage type for defining message structures
|
|
125
|
+
- **FR-003**: System MUST export A2UIAction type for defining action payloads
|
|
126
|
+
- **FR-004**: A2UIRender MUST render all components defined in the messages array
|
|
127
|
+
- **FR-005**: A2UIRender MUST invoke onAction callback when user interactions trigger actions
|
|
128
|
+
- **FR-006**: A2UIRender MUST accept an optional components prop (ComponentsMap) for custom component overrides
|
|
129
|
+
- **FR-007**: System MUST export useDispatchAction hook for custom components to dispatch actions
|
|
130
|
+
- **FR-008**: System MUST export ComponentRenderer component for rendering child components within custom components
|
|
131
|
+
- **FR-009**: System MUST export useDataBinding hook for reading data from the A2UI context
|
|
132
|
+
- **FR-010**: System MUST export useFormBinding hook for two-way data binding in form components
|
|
133
|
+
- **FR-011**: useDispatchAction hook MUST accept surfaceId, componentId, and action parameters
|
|
134
|
+
- **FR-012**: useDataBinding hook MUST accept surfaceId, binding path, and default value parameters
|
|
135
|
+
- **FR-013**: useFormBinding hook MUST return a tuple of [currentValue, setValue] for two-way binding
|
|
136
|
+
- **FR-014**: ComponentRenderer MUST accept surfaceId and componentId props to render specific components
|
|
137
|
+
- **FR-015**: Custom components MUST receive surfaceId and componentId as props for context identification
|
|
138
|
+
|
|
139
|
+
### Key Entities
|
|
140
|
+
|
|
141
|
+
- **A2UIMessage**: Represents a message containing UI component definitions to be rendered. Contains component tree structure and associated data.
|
|
142
|
+
- **A2UIAction**: Represents an action payload triggered by user interaction. Contains action type and associated data for the callback.
|
|
143
|
+
- **ComponentsMap**: A Map structure mapping component type names (strings) to React component implementations. Used for overriding defaults and adding custom components.
|
|
144
|
+
- **Surface**: A rendering context identified by surfaceId that contains components and their associated data bindings.
|
|
145
|
+
- **Component**: An individual UI element within a surface, identified by componentId, with properties specific to its type.
|
|
146
|
+
|
|
147
|
+
## Success Criteria _(mandatory)_
|
|
148
|
+
|
|
149
|
+
### Measurable Outcomes
|
|
150
|
+
|
|
151
|
+
- **SC-001**: Developers can render A2UI messages with a single component import and minimal configuration (under 10 lines of code for basic usage)
|
|
152
|
+
- **SC-002**: All user interactions on rendered components trigger the onAction callback within 100ms of user input
|
|
153
|
+
- **SC-003**: Custom component overrides work without modifying library source code
|
|
154
|
+
- **SC-004**: Custom components can access all necessary hooks (useDispatchAction, useDataBinding, useFormBinding) from the library exports
|
|
155
|
+
- **SC-005**: The library exports are accessible via the versioned path '@easyops-cn/a2ui-react/0.8'
|
|
156
|
+
- **SC-006**: Nested component rendering works to at least 10 levels deep without performance degradation
|
|
157
|
+
- **SC-007**: Form bindings reflect value changes immediately (within one render cycle)
|
|
158
|
+
|
|
159
|
+
## Assumptions
|
|
160
|
+
|
|
161
|
+
- This library defines and owns the A2UI protocol message format types (A2UIMessage, A2UIAction, Surface, Component schemas)
|
|
162
|
+
- React 18+ is the target runtime environment
|
|
163
|
+
- TypeScript types are required for all public exports
|
|
164
|
+
- Default components will be provided for common UI elements (Button, Text, etc.)
|
|
165
|
+
- The versioned export path pattern (@easyops-cn/a2ui-react/0.8) follows npm package subpath exports convention
|