history/260927-ComplexWebAppUiComponents_chatgpt.txt is a pasted AI-chat transcript on UI-integration architecture. It fails this repo's primary-source bar (unauthored, unverifiable broken citation markers), so it is screened as a leads-only document, not cataloged as a source, and never cited as evidence. Instead read the two real specs it pointed to: MDN Custom Elements (SRC-0046) and webpack Module Federation (SRC-0047). Neither defines a semantic UI capability contract (inputs/events/commands/slots) as a first-class concept; that gap motivates the new draft hypothesis, distinct from the existing Independent Feature Boundary (release cadence) and Vertical Feature Slice (source grouping) candidates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 290719@bnt-lap001 Assistant-Session: 55cbe504-1bfe-4932-b761-29016523c87b
480 lines
No EOL
13 KiB
Text
Executable file
480 lines
No EOL
13 KiB
Text
Executable file
## Prompt
|
|
|
|
Is there a best practice for providing ui components to complex web application ui environments integrating multiple user interfaces and interface components?
|
|
|
|
## Exploration
|
|
|
|
Yes. For a complex web application that needs to integrate multiple user interfaces, teams, frameworks, or independently evolving components, there is a fairly mature set of practices—but not one single pattern. The useful way to think about it is as an **integration architecture with several layers**, rather than merely as a component library.
|
|
|
|
A good default architecture today looks roughly like this:
|
|
|
|
```text
|
|
Application Shell
|
|
│
|
|
├── UX / Design System
|
|
│ ├── design tokens
|
|
│ ├── primitive components
|
|
│ └── interaction conventions
|
|
│
|
|
├── UI Integration Contract
|
|
│ ├── inputs / properties
|
|
│ ├── outputs / events
|
|
│ ├── commands
|
|
│ ├── lifecycle
|
|
│ └── capability metadata
|
|
│
|
|
├── Application Components
|
|
│ ├── search-ui
|
|
│ ├── customer-editor
|
|
│ ├── document-viewer
|
|
│ └── reporting-ui
|
|
│
|
|
├── Composition Layer
|
|
│ ├── routing
|
|
│ ├── layout
|
|
│ ├── authentication context
|
|
│ ├── shared state
|
|
│ └── event/message bus
|
|
│
|
|
└── Runtime Integration
|
|
├── ordinary packages
|
|
├── Web Components
|
|
├── module federation
|
|
├── micro-frontends
|
|
└── iframe/process isolation when necessary
|
|
```
|
|
|
|
The important distinction is that these layers solve **different problems**.
|
|
|
|
### 1. Use a design system for visual and interaction coherence
|
|
|
|
A design system should define things such as typography, spacing, colors, accessibility conventions, buttons, dialogs, forms, navigation, tables, notifications, etc.
|
|
|
|
But a design system alone is not enough. It tells you that all applications should use the same sort of `<Button>`; it does not tell the host application how a `CustomerEditor` announces that a customer was saved.
|
|
|
|
I would therefore keep **design-system contracts** separate from **application-component contracts**.
|
|
|
|
---
|
|
|
|
### 2. Prefer framework-neutral contracts at integration boundaries
|
|
|
|
Inside an application, React-to-React or Vue-to-Vue component APIs are perfectly reasonable.
|
|
|
|
Across independently developed systems, however, tying the integration contract to:
|
|
|
|
```tsx
|
|
<CustomerEditor
|
|
customer={customer}
|
|
onSaved={...}
|
|
/>
|
|
```
|
|
|
|
creates fairly strong coupling to React, its version, its rendering model, and often its state-management conventions.
|
|
|
|
A more durable boundary is something conceptually like:
|
|
|
|
```html
|
|
<customer-editor customer-id="4711">
|
|
</customer-editor>
|
|
```
|
|
|
|
with:
|
|
|
|
```text
|
|
attributes / properties -> configuration and input
|
|
DOM events -> observations/output
|
|
methods -> explicit commands
|
|
slots -> host-provided presentation
|
|
CSS custom properties -> theming
|
|
```
|
|
|
|
That is exactly the architectural territory that **Web Components** were designed for. Custom Elements give you browser-native component boundaries, while Shadow DOM can provide DOM/CSS isolation. :chatgpt-content-reference{index="0"}
|
|
|
|
This doesn't imply that you have to write applications without React, Vue, Svelte, Lit, etc. A Web Component can simply be the **published interoperability surface** around an implementation written using another technology.
|
|
|
|
---
|
|
|
|
### 3. Treat coarse-grained UI capabilities differently from widgets
|
|
|
|
There is an important granularity boundary.
|
|
|
|
Things such as:
|
|
|
|
```text
|
|
Button
|
|
Checkbox
|
|
DatePicker
|
|
Popover
|
|
DataGrid
|
|
```
|
|
|
|
are design-system components.
|
|
|
|
Things such as:
|
|
|
|
```text
|
|
CustomerSearch
|
|
InvoiceEditor
|
|
DocumentViewer
|
|
UserAdministration
|
|
OrderWorkbench
|
|
```
|
|
|
|
are better thought of as **UI capabilities**.
|
|
|
|
The latter should have explicit contracts and should often be independently versionable.
|
|
|
|
For example:
|
|
|
|
```yaml
|
|
component: customer-editor
|
|
version: 2.3
|
|
|
|
requires:
|
|
- identity.user
|
|
- customer.read
|
|
- customer.write
|
|
|
|
inputs:
|
|
customerId: string
|
|
mode: [view, edit]
|
|
|
|
events:
|
|
- customer-selected
|
|
- customer-updated
|
|
- editing-cancelled
|
|
|
|
commands:
|
|
- reload
|
|
- focus
|
|
- reset
|
|
|
|
slots:
|
|
- toolbar
|
|
- footer
|
|
|
|
context:
|
|
- locale
|
|
- theme
|
|
- tenant
|
|
```
|
|
|
|
This starts to look less like a component API and more like a **small interface definition language for UI capabilities**. For large systems, I think this is the more useful abstraction.
|
|
|
|
---
|
|
|
|
### 4. Separate *composition* from *implementation*
|
|
|
|
A common architectural mistake is letting every component know how the application fits together.
|
|
|
|
Instead:
|
|
|
|
```text
|
|
Component:
|
|
"I can edit a customer."
|
|
|
|
Shell:
|
|
"You appear here,
|
|
for this tenant,
|
|
with this identity,
|
|
at this route,
|
|
beside these other capabilities."
|
|
```
|
|
|
|
The application shell should generally own:
|
|
|
|
- routing
|
|
- navigation
|
|
- page layout
|
|
- authentication/session context
|
|
- authorization decisions
|
|
- locale
|
|
- theme
|
|
- notifications
|
|
- observability
|
|
- feature flags
|
|
- capability discovery
|
|
- failure boundaries
|
|
|
|
Individual UI capabilities should own their domain interaction.
|
|
|
|
This is very similar to the distinction between a **control plane** and an **execution plane**.
|
|
|
|
---
|
|
|
|
### 5. Use events rather than shared mutable state across boundaries
|
|
|
|
Within one frontend, a Redux/Zustand/Pinia/etc. store can be useful.
|
|
|
|
Across independently evolving UI components, a giant shared store tends to become an implicit API.
|
|
|
|
Instead of:
|
|
|
|
```text
|
|
component A
|
|
↓
|
|
global state object
|
|
↑
|
|
component B
|
|
```
|
|
|
|
prefer:
|
|
|
|
```text
|
|
CustomerSearch
|
|
│
|
|
│ customer-selected
|
|
↓
|
|
Application Shell
|
|
│
|
|
│ selectedCustomer=123
|
|
↓
|
|
CustomerEditor
|
|
```
|
|
|
|
or an event/message mechanism where appropriate.
|
|
|
|
The semantic distinction is useful:
|
|
|
|
```text
|
|
properties desired state/configuration
|
|
events things that happened
|
|
commands explicit requests
|
|
queries information requests
|
|
```
|
|
|
|
That simple vocabulary dramatically improves UI contracts.
|
|
|
|
---
|
|
|
|
### 6. Use isolation deliberately rather than universally
|
|
|
|
There is a spectrum:
|
|
|
|
```text
|
|
Shared source/component
|
|
↓
|
|
npm package
|
|
↓
|
|
Web Component
|
|
↓
|
|
runtime remote module
|
|
↓
|
|
micro-frontend
|
|
↓
|
|
iframe
|
|
```
|
|
|
|
Each step provides more autonomy/isolation, but also introduces more integration cost.
|
|
|
|
**Web Components** are particularly attractive for reusable framework-independent components because Shadow DOM can prevent CSS and DOM internals from colliding with their host application. :chatgpt-content-reference{index="1"}
|
|
|
|
For larger independently deployed application fragments, **Module Federation** is another established technique. Webpack explicitly describes its purpose as allowing separate builds to expose and consume modules at runtime, including the micro-frontend use case. :chatgpt-content-reference{index="2"}
|
|
|
|
I would not make either the universal solution.
|
|
|
|
A useful rule is:
|
|
|
|
> Use the weakest isolation mechanism that provides sufficient organizational and technical independence.
|
|
|
|
---
|
|
|
|
### 7. Avoid the "micro-frontends everywhere" trap
|
|
|
|
Micro-frontends are useful when there is genuinely independent ownership:
|
|
|
|
```text
|
|
Team A Team B Team C
|
|
Billing CRM Analytics
|
|
release A release B release C
|
|
\ | /
|
|
Application Shell
|
|
```
|
|
|
|
They are much less attractive when one team owns everything. You otherwise pay for:
|
|
|
|
- duplicate dependencies
|
|
- runtime orchestration
|
|
- version incompatibilities
|
|
- inconsistent UX
|
|
- authentication propagation
|
|
- routing complexity
|
|
- performance overhead
|
|
- distributed debugging
|
|
|
|
without getting much organizational independence in return.
|
|
|
|
---
|
|
|
|
### 8. Make context explicit
|
|
|
|
One pattern I particularly recommend for complex platforms is a small, stable **host context**:
|
|
|
|
```ts
|
|
interface UIHostContext {
|
|
identity
|
|
tenant
|
|
locale
|
|
theme
|
|
permissions
|
|
navigation
|
|
notifications
|
|
telemetry
|
|
}
|
|
```
|
|
|
|
But don't expose an enormous application object.
|
|
|
|
Instead expose capabilities:
|
|
|
|
```text
|
|
context.identity.currentUser()
|
|
context.navigation.open(...)
|
|
context.notifications.publish(...)
|
|
context.telemetry.record(...)
|
|
```
|
|
|
|
This gives embedded components access to platform services without allowing them to become coupled to the entire host implementation.
|
|
|
|
---
|
|
|
|
### 9. Explicitly distinguish several kinds of compatibility
|
|
|
|
A UI component can be technically loadable and nevertheless incompatible.
|
|
|
|
I would track at least:
|
|
|
|
```text
|
|
Protocol compatibility
|
|
Can host and component communicate?
|
|
|
|
Semantic compatibility
|
|
Do "customer-selected" and "customerId" mean the same thing?
|
|
|
|
Visual compatibility
|
|
Does it fit the design system?
|
|
|
|
Interaction compatibility
|
|
Keyboard, focus, navigation, modal behavior, etc.
|
|
|
|
Security compatibility
|
|
Does it respect identity/authorization boundaries?
|
|
|
|
Lifecycle compatibility
|
|
Can it mount, unmount, reconnect and upgrade safely?
|
|
|
|
Operational compatibility
|
|
Can it be observed, debugged and degraded independently?
|
|
```
|
|
|
|
This becomes important once components are reused across several applications.
|
|
|
|
---
|
|
|
|
## The architecture I would favour
|
|
|
|
For a modern platform with many interfaces, I would probably choose this combination:
|
|
|
|
```text
|
|
UI PLATFORM
|
|
|
|
┌──────────┐
|
|
│ App Shell │
|
|
└────┬─────┘
|
|
│
|
|
┌────────────────┼────────────────┐
|
|
│ │ │
|
|
routing UI context capability
|
|
layout identity registry
|
|
navigation theme discovery
|
|
locale
|
|
│
|
|
┌────────────┴────────────┐
|
|
│ UI Component Contract │
|
|
│ │
|
|
│ props / events │
|
|
│ commands / slots │
|
|
│ lifecycle / metadata │
|
|
└────────────┬────────────┘
|
|
│
|
|
┌─────────────────────┼────────────────────┐
|
|
│ │ │
|
|
Native Web Component Microfrontend
|
|
component boundary boundary
|
|
│ │ │
|
|
React React/Vue/etc independently
|
|
Vue internally deployed
|
|
Svelte
|
|
```
|
|
|
|
Underneath all of this sits a shared:
|
|
|
|
```text
|
|
Design Language
|
|
↓
|
|
Design Tokens
|
|
↓
|
|
Primitive Component Library
|
|
↓
|
|
Accessibility & interaction rules
|
|
```
|
|
|
|
The browser platform is becoming an increasingly credible basis for that middle interoperability layer. Web Components provide native reusable elements; Shadow DOM supplies encapsulation; slots provide host/component composition; and scoped custom-element registries address one of the historical problems of large compositions—global custom-element naming collisions. :chatgpt-content-reference{index="3"}
|
|
|
|
### One additional concept I think is worth making explicit
|
|
|
|
Given the way you tend to approach interface architecture, I would distinguish between a **UI component** and an **Interface Capability**.
|
|
|
|
An Interface Capability would be:
|
|
|
|
> A self-describing unit of human-system interaction that exposes a stable semantic contract independently of its rendering implementation.
|
|
|
|
For example:
|
|
|
|
```text
|
|
InterfaceCapability: DocumentViewer
|
|
|
|
provides:
|
|
view-document
|
|
browse-pages
|
|
download-document
|
|
|
|
accepts:
|
|
document-reference
|
|
page-reference
|
|
|
|
emits:
|
|
page-changed
|
|
document-opened
|
|
download-requested
|
|
|
|
requires:
|
|
document.read
|
|
|
|
presentation:
|
|
embeddable
|
|
fullscreen
|
|
|
|
implementations:
|
|
web-component
|
|
react-component
|
|
standalone-webapp
|
|
```
|
|
|
|
That abstraction scales much better than thinking exclusively in terms of React components or micro-frontends. A component becomes **one implementation of an interface capability**.
|
|
|
|
And that in turn gives you a rather elegant architecture:
|
|
|
|
```text
|
|
Interface Capability
|
|
↓
|
|
semantic contract
|
|
↓
|
|
one or more UI implementations
|
|
↓
|
|
composition/runtime adapter
|
|
↓
|
|
host application
|
|
```
|
|
|
|
This is close to what I would regard as the current best-practice direction for a genuinely complex multi-interface platform: **design system below, semantic capability contracts in the middle, composition shell above, and implementation technology deliberately hidden behind the boundary.** |