experimental web port of the Union style engine + adapters
styling theming css solidjs react union kde
0

Configure Feed

Select the types of activity you want to include in your feed.

docs: rewrite upstream-architecture, trim AI traces in plugin docs and pitfalls

- upstream-architecture.md: full rewrite (318→105 lines), MDN style
- react-plugin.md: remove pedagogical preamble, keep reference content
- solid-plugin.md: remove pedagogical preamble, keep reference content
- pitfalls.md: condense §20 and §21 (Positioner architecture explanations)
- index.md: add upstream-architecture link

StyledRect design docs intentionally left unstaged.

+137 -23
+1
docs/index.md
··· 23 23 - [CSS Input Support](./css-input.md) — What CSS features from Union's input format are supported. 24 24 - [Solid Plugin](./solid-plugin.md) — `@union-web/solid-plugin` API (current focus). 25 25 - [React Plugin](./react-plugin.md) — `@union-web/react-plugin` API (may lag behind solid). 26 + - [Upstream Architecture](./upstream-architecture.md) — How native Union's pieces fit together (Positioner, StyledRectangle, element architecture, CSS alignment groups). 26 27 - [Pitfalls](./pitfalls.md) — Hard-won lessons from developing the plugin architecture.
+21
docs/pitfalls.md
··· 349 349 - `hint` → `.name` (`.level-1`) 350 350 - `state` → `:name` (`:hovered`) 351 351 - `id` → `#name` (`#myid`) 352 + 353 + ## 20. Upstream Union: Positioner Is NOT a Page Layout System 354 + 355 + Positioner is a micro-layout for control internals, never page layout. 356 + `Union.Positioner.positionItems` is only set to a control's own internal children 357 + (e.g., Button: `[contentItem, indicator]`, ComboBox: `[contentItem, icon, indicator]`). 358 + Controls are positioned within a parent by Qt Quick Layouts (RowLayout, ColumnLayout) 359 + or anchoring — never by a parent's Positioner. 360 + 361 + The `button { layout-alignment: content fill fill 0; }` CSS rule is effectively dead 362 + code — it IS compiled into the Button's style properties, but no real parent Positioner 363 + ever reads it. The only non-internal usage is `examples/snippets/Positioner.qml`, a 364 + demo that specifically tests the API. 365 + 366 + ## 21. Upstream Union: `positionChildren` Recursion Handles Control Internals 367 + 368 + `Union.PositionedItem.positionChildren: true` on a `contentItem` tells the parent's 369 + Positioner to skip the contentItem itself and instead directly position its children 370 + (Icon, Text). This is how Button.qml's Positioner sees Icon and Text without 371 + explicitly listing them — the DefaultContentItem passes them through. 372 + See `src/output/qtquick/plugin/positioner/Positioner.cpp`.
+5 -14
docs/react-plugin.md
··· 14 14 15 15 ## How It Adapts Union's API 16 16 17 - Native Union's output plugins (QtQuick, QtWidgets) query styles for each widget using `Union::ElementQuery`. The widget hierarchy is implicitly known — Qt maintains a tree of `QQuickItem` or `QWidget` instances. An output plugin creates `Union::Element` objects for each widget, feeds them to `ElementQuery`, and applies the returned properties via the platform's rendering system. 18 - 19 - The web has no such built-in element tree. React components compose freely — a `<Button>` can appear inside a `<Page>`, a `<Toolbar>`, a `<Card>`, or any custom wrapper. There is no framework-level "this button is inside this page" relationship. 20 - 21 - The react-plugin recovers this hierarchy through **explicit ancestor context**: 17 + The web has no built-in element tree. The plugin recovers hierarchy through **explicit ancestor context**: 22 18 23 19 | Union Concept | React Plugin Equivalent | 24 20 |-------------|------------------------| 25 - | `Union::Element` constructed by output plugin | `createElement(role, opts)` called inside `useUnion()` | 26 - | `Union::ElementQuery` | `resolveElement()` from core, called internally by `useUnion()` | 27 - | Widget tree (implicit in Qt) | Explicit `UnionProvider` chain via React context | 21 + | `Union::Element` | `createElement(role, opts)` inside `useUnion()` | 22 + | `Union::ElementQuery` | `resolveElement()` from core | 23 + | Widget tree (implicit in Qt) | `UnionProvider` chain via React context | 28 24 | `Union::StyleRule` collection | `StyleRule[]` from `UnionIRContext` | 29 - | Platform renderer applies styles | `styleGroupToReact()` converts IR to React `CSSProperties` | 30 25 31 - Components call `useUnion()` to declare themselves to the styling system and wrap their children in the returned `UnionProvider` to extend the ancestor chain. This allows selectors like `textarea > .placeholder` to match correctly because `Placeholder`'s `useUnion` call can see `textarea` in its ancestor context. 32 - 33 - ## Why Explicit Style-Node Registration? 34 - 35 - Because React allows free-form application composition, the plugin cannot assume a fixed element hierarchy. By requiring each component to call `useUnion()` and render `UnionProvider`, the system builds a correct ancestor chain regardless of nesting. This also enables custom components — any third-party component can opt in to Union theming by calling `useUnion()` with the appropriate role, without the theme engine needing to know about the component in advance. In practice, most usage will be through a dedicated component library built on top of `react-plugin`. 26 + Components call `useUnion()` and wrap children in the returned `UnionProvider` to extend the ancestor chain. This enables selectors like `textarea > .placeholder` to match because `Placeholder` can see `textarea` in its ancestor context. Any third-party component can opt in to Union theming by calling `useUnion()` with the appropriate role. 36 27 37 28 ## API Reference 38 29
+5 -9
docs/solid-plugin.md
··· 10 10 11 11 ## Architecture 12 12 13 - Native Union's output plugins (QtQuick, QtWidgets) query styles via `Union::ElementQuery`, 14 - with the widget hierarchy implicitly tracked by Qt's tree. The web has no such built-in 15 - tree — Solid components compose freely. 16 - 17 - The Solid plugin recovers this hierarchy through **explicit ancestor context**: 13 + The web has no built-in element tree. The plugin recovers hierarchy through **explicit ancestor context**: 18 14 19 15 | Union Concept | Solid Plugin Equivalent | 20 16 |-------------|------------------------| 21 - | `Union::Element` | `createElement(role, opts)` called inside `useUnion()` | 22 - | `Union::ElementQuery` | `resolveElement()` from core, called internally by `useUnion()` | 23 - | Widget tree (implicit in Qt) | Explicit `UnionProvider` chain via Solid context | 17 + | `Union::Element` | `createElement(role, opts)` inside `useUnion()` | 18 + | `Union::ElementQuery` | `resolveElement()` from core | 19 + | Widget tree (implicit in Qt) | `UnionProvider` chain via Solid context | 24 20 | `Union::StyleRule` collection | `StyleRule[]` from `UnionIRContext` | 25 - | Platform renderer applies styles | `styleGroupToUnionStyleProps()` from core, converts IR to `UnionStyleProps`, rendered by `StyledRect` | 21 + | Platform renderer applies styles | `styleGroupToUnionStyleProps()` → `UnionStyleProps` → `StyledRect` | 26 22 27 23 ## Key Differences from React Plugin 28 24
+105
docs/upstream-architecture.md
··· 1 + # Union Upstream Architecture 2 + 3 + Union is a theming engine (not a UI framework). It provides a CSS-based styling system for Qt Quick Controls 2 and Kirigami. This document describes the native C++ architecture to clarify what Union Web must replicate. 4 + 5 + ## Processing Pipeline 6 + 7 + ``` 8 + CSS files (.css) 9 + 10 + StyleRule[] + Variables 11 + 12 + QuickStyle (attached QML property) — queries StylePropertyGroup for a QQuickItem 13 + 14 + Output Plugin (QtQuick): 15 + ├── PositionerLayout — reads layout/icon/text alignment, positions children 16 + └── StyledRectangle — reads background/border/outline/shadow, renders visuals 17 + ``` 18 + 19 + The CSS-to-property pipeline is shared; two independent output systems consume different subsets of each `StylePropertyGroup`. 20 + 21 + ## PositionerLayout 22 + 23 + A micro-layout engine that positions a control's internal children (icon, text, indicator). Not a page-level layout system. 24 + 25 + - **Scope:** Every control's `Union.Positioner.positionItems` — always internal children like `[contentItem, indicator]`, never sibling controls. 26 + - **Control-to-control layout** is done by Qt Quick Layouts (RowLayout, ColumnLayout) or anchoring, never by Positioner. 27 + - **Computes:** x/y positions within the control's bounds, then calls `item->setX()`/`setY()`/`setSize()` on child QQuickItems. 28 + - **Alignment source:** Each positioned item reads from one of three CSS property groups based on its `PositionedItem.source`: 29 + 30 + | Source | CSS property group | 31 + |--------|--------------------| 32 + | Layout | `layout.alignment` | 33 + | Icon | `icon.alignment` | 34 + | Text | `text.alignment` | 35 + 36 + ### Three containers (coordinate reference frames) 37 + 38 + ``` 39 + Layout { 40 + itemContainer: // full parent box 41 + ├── start bucket 42 + ├── center bucket 43 + ├── end bucket 44 + └── fill bucket 45 + backgroundContainer: // parent minus start/end, offset by inset 46 + contentContainer: // parent minus start/end, offset by padding 47 + } 48 + ``` 49 + 50 + These are **math rectangles**, not visual elements. They exist only during `Layout::layout()` computation and are discarded after item positions are computed. 51 + 52 + ## StyledRectangle 53 + 54 + A C++ QQuickItem that renders visual chrome via QSG (Qt Scene Graph) nodes. 55 + 56 + - **Scope:** Used as `background:` on every control (64 QML files in the theme). 57 + - **Consumes:** `background.*`, `border.*`, `outline.*`, `shadow.*`, `corner_radius.*`, `width`, `height`, `min-width`, `min-height`. 58 + - **Positioning:** Automatically sized to fill the control by `QQuickControl`'s base class. 59 + - **Never in Positioner:** StyledRectangle is never in any `positionItems`. 60 + 61 + StyledRectangle-only controls (e.g., ApplicationWindow, Page) have no Positioner — they render visual chrome only, with child layout delegated to Qt Quick Layouts. 62 + 63 + ## Three-Layer Control Architecture 64 + 65 + Every visual control has the same internal structure (Button example): 66 + 67 + ``` 68 + Button (QQuickAbstractButton) 69 + 70 + ├── background: StyledRectangle ← visual chrome (border, fill, shadow) 71 + │ [NOT in Positioner] 72 + 73 + ├── contentItem: DefaultContentItem ← semantic content container 74 + │ [IN Positioner.positionItems[0]; positionChildren: true] 75 + │ │ 76 + │ ├── Icon (source: Icon) ← positioned via icon.alignment 77 + │ └── Text (source: Text) ← positioned via text.alignment 78 + 79 + └── indicator: Union.Icon ← optional visual indicator 80 + [IN Positioner.positionItems[1]; source: Layout] 81 + [positioned via layout.alignment] 82 + ``` 83 + 84 + | Layer | QML property | Managed by | CSS group | 85 + |-------|-------------|------------|-----------| 86 + | Background | `background:` | `QQuickControl` (fills control) | `background.*`, `border.*`, `outline.*`, `shadow.*` | 87 + | Content container | `contentItem:` | Positioner (via `positionChildren: true`) | N/A (pass-through) | 88 + | Icon | child of contentItem | Positioner (source: Icon) | `icon.alignment` | 89 + | Text | child of contentItem | Positioner (source: Text) | `text.alignment` | 90 + | Indicator | `indicator:` | Positioner (source: Layout) | `layout.alignment` | 91 + 92 + ## Background Naming Collision 93 + 94 + Two unrelated concepts share the name "background": 95 + 96 + - **`Layout::backgroundContainer`** — a math rectangle in Positioner's layout computation. Child items with `layout-alignment-container: background` are positioned relative to this inset-offset frame. Nothing renders here. 97 + - **`control.background`** — a visual `StyledRectangle` QQuickItem that renders the control's background, border, outline, and shadow. It is never in any `positionItems`. 98 + 99 + ## Key Takeaways for Web Ports 100 + 101 + - Positioner is irrelevant for page-level layouts. Union Web positions controls using CSS flexbox/grid. The Positioner engine is only needed for internal control child layout (icon + text + indicator). 102 + - `layout-alignment` on standalone controls like buttons is effectively dead code — controls are positioned by Qt Quick Layouts, not a parent Positioner. 103 + - StyledRectangle is the visual heart of every control. Without it, controls have no background, border, shadow, or outline. 104 + - Positioner's three-container model can collapse to a single content container for web ports, since `itemContainer` and `backgroundContainer` are math frames that don't correspond to visual layers. 105 + - The element YAML files at `tools/elementdocgenerator/*.yml` define every Union element's states, hints, attributes, and sub-elements — they are the definitive reference.